Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ROUP

Rust-based OpenMP & OpenACC Parser

Safe, fast, and comprehensive directive parsing

Get Started · Tutorials · API Reference · GitHub


What is ROUP?

ROUP is an experimental parser for OpenMP and OpenACC directives, written in safe Rust with C, C++, and Fortran bindings. Parse pragmas like #pragma omp parallel for or !$acc parallel into structured data that your tools can analyze, transform, and process.

⚠️ Experimental Status: ROUP is under active development and not yet production-ready. APIs may change, and some OpenMP features are still being implemented. Use in research and experimental projects.

Perfect for:

  • 🔧 Compiler research - Experiment with OpenMP parsing in compilers
  • 🔍 Analysis prototypes - Build experimental linters and analyzers
  • 🎓 Researchers - Study parallelization patterns and test new ideas
  • 📚 Educators - Teaching tool for parallel programming concepts

Why ROUP?

🚀 Fast & Lightweight

  • Zero-copy lexer and hand-written parsers
  • Standalone library (no LLVM/ANTLR dependencies)
  • Compatibility shims for ompparser and accparser

🛡️ Safe & Reliable

  • Memory safety guaranteed except for the narrow FFI boundary
  • Extensive automated tests plus OpenMP_VV/OpenACCV-V validation and compat ctests
  • NULL-safe C API with defensive checks

📚 Comprehensive Directive Support

  • OpenMP 3.0–6.0 directives, clauses, combined forms, and metadirectives
  • OpenACC 3.4 directives, clauses, aliases, and end-paired constructs
  • Canonical keyword handling and clause alias preservation
  • Interactive parser debugger (roup_debug) for OpenMP/OpenACC (C and Fortran sentinels)

🔌 Multi-Language APIs

LanguageAPI StyleMemory ManagementStatus
RustNativeAutomatic (ownership)
CPointer-basedManual (malloc/free pattern)
C++RAII wrappersAutomatic (destructors)
FortranC interopManual (via iso_c_binding)✅ (via C API)

Quick Example

Parse in 3 Lines (Rust)

use roup::parser::openmp;

let parser = openmp::parser();
let (_, directive) = parser.parse("#pragma omp parallel for num_threads(4)").unwrap();
// Access directive information
println!("Directive: {}", directive.name);  // Output: Directive: parallel for
println!("Found {} clauses", directive.clauses.len());  // Output: Found 1 clauses
// Iterate through clauses
for clause in &directive.clauses {
    println!("  Clause: {}", clause.name);  // Output:   Clause: num_threads
}

Parse in C

OmpDirective* dir = roup_parse("#pragma omp parallel for num_threads(4)");
printf("Clauses: %d\n", roup_directive_clause_count(dir));
roup_directive_free(dir);

Parse in C++ (with RAII)

roup::Directive dir("#pragma omp parallel for num_threads(4)");
std::cout << "Clauses: " << dir.clause_count() << "\n";
// Automatic cleanup!

Parse Fortran

! Free-form Fortran
directive_ptr = roup_parse_with_language("!$OMP PARALLEL PRIVATE(A)", &
                                          ROUP_LANG_FORTRAN_FREE)

See full examples →


Feature Highlights

🎯 Comprehensive Coverage

Parallel Constructs - 20+ directives
  • parallel - Basic parallel regions
  • parallel for - Combined parallel + worksharing
  • parallel sections - Parallel sections
  • parallel master - Parallel master thread
  • parallel loop - OpenMP 5.0+ parallel loop
  • And more…
Work-Sharing - 10+ directives
  • for / do - Loop worksharing
  • sections / section - Code sections
  • single - Execute once
  • workshare - Fortran worksharing
  • loop - Generic loop construct
Tasking - 15+ directives
  • task - Explicit tasks
  • taskloop - Loop-based tasks
  • taskgroup - Task synchronization
  • taskwait - Wait for tasks
  • taskyield - Yield to other tasks
  • Dependency clauses: depend, priority, detach
Device Offloading - 25+ directives
  • target - Offload to device
  • target data - Device data management
  • target enter/exit data - Data transfer
  • target update - Synchronize data
  • teams - Multiple thread teams
  • distribute - Distribute iterations
SIMD - 10+ directives
  • simd - SIMD loops
  • declare simd - Vectorizable functions
  • distribute simd - Combined distribute + SIMD
  • Various alignment and vectorization clauses
Advanced (OpenMP 5.0+)
  • metadirective - Context-sensitive directives
  • declare variant - Function variants
  • loop - Generic loop construct
  • scan - Prefix scan operations
  • assume - Compiler assumptions

Full OpenMP Support Matrix →

🔍 Rich Clause Support

92+ clause types including:

CategoryClauses
Data Sharingprivate, shared, firstprivate, lastprivate
Reductionsreduction(+:x), reduction(min:y), custom operators
Schedulingschedule(static), schedule(dynamic,100), collapse(3)
Controlif(condition), num_threads(8), proc_bind(close)
Devicemap(to:x), device(2), defaultmap(tofrom:scalar)
Dependenciesdepend(in:x), depend(out:y), depend(inout:z)

Complete clause reference → | Edge cases | Handled (fuzzing tested) | Likely has bugs | | Spec compliance | Verified | Uncertain |

Verdict: Unless you have very specific needs, use ROUP.


Safety Guarantees

ROUP prioritizes safety without compromising usability:

Memory Safety

  • No buffer overflows - Rust prevents at compile time
  • No use-after-free - Ownership system enforces
  • No double-free - Checked at FFI boundary
  • No memory leaks - RAII and destructors
  • No data races - Thread-safe parsing

API Safety

Rust API:

  • 100% memory-safe by construction
  • Impossible to trigger undefined behavior

C API:

  • NULL checks before all pointer operations
  • Returns safe defaults on error (-1, NULL)
  • Validates UTF-8 encoding
  • Documents all safety contracts

Getting Started

Choose your language:

🦀 Rust

Install:

[dependencies]
roup = "0.7"

Learn:

🔧 C

Build:

cargo build --release

Learn:

⚙️ C++

Build:

cargo build --release

Learn:

Quick Start Guide →


Community


License

ROUP is open source under the MIT License.

Copyright © 2024-2025 Anjia Wang


Next Steps


Why ROUP?

For Compiler Developers

  • Drop-in OpenMP/OpenACC parser component
  • Well-tested, battle-hardened parsing logic
  • Easy FFI integration from any language

For Tool Builders

  • Analyze OpenMP code without a full compiler
  • Build linters, formatters, and code analyzers
  • Extract parallelization patterns from codebases

For Researchers

  • Study directive usage patterns
  • Prototype new directive extensions
  • Educational tool for learning parallel programming

Quick Example

Rust

use roup::parser::openmp;

let parser = openmp::parser();
let input = "#pragma omp parallel for num_threads(4) private(i)";
match parser.parse(input) {
    Ok((_, directive)) => {
        println!("Directive: {:?}", directive.kind);
        println!("Clauses: {}", directive.clauses.len());
    }
    Err(e) => eprintln!("Parse error: {:?}", e),
}

C

#include <stdio.h>

// Forward declarations
typedef struct OmpDirective OmpDirective;
extern OmpDirective* roup_parse(const char* input);
extern int32_t roup_directive_clause_count(const OmpDirective* dir);
extern void roup_directive_free(OmpDirective* dir);

int main() {
    OmpDirective* dir = roup_parse("#pragma omp parallel for num_threads(4)");
    if (dir) {
        printf("Clauses: %d\n", roup_directive_clause_count(dir));
        roup_directive_free(dir);
    }
    return 0;
}

C++

#include <iostream>
#include <memory>

struct OmpDirective;
extern "C" {
    OmpDirective* roup_parse(const char* input);
    int32_t roup_directive_clause_count(const OmpDirective* dir);
    void roup_directive_free(OmpDirective* dir);
}

// RAII wrapper
class Directive {
    OmpDirective* ptr_;
public:
    explicit Directive(const char* input) : ptr_(roup_parse(input)) {}
    ~Directive() { if (ptr_) roup_directive_free(ptr_); }
    bool valid() const { return ptr_ != nullptr; }
    int clause_count() const { 
        return ptr_ ? roup_directive_clause_count(ptr_) : 0; 
    }
};

int main() {
    Directive dir("#pragma omp parallel for num_threads(4)");
    if (dir.valid()) {
        std::cout << "Clauses: " << dir.clause_count() << "\n";
    }
    return 0;
}

Architecture

ROUP uses a clean, modular architecture:

┌─────────────────────────────────────────┐
│         Application Layer               │
│  (Your compiler/tool/analyzer)          │
└─────────────────┬───────────────────────┘
                  │
      ┌───────────┼───────────┐
      │           │           │
      ▼           ▼           ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Rust API│ │  C API  │ │ C++ API │
│         │ │         │ │ (RAII)  │
└─────────┘ └─────────┘ └─────────┘
      │           │           │
      └───────────┼───────────┘
                  │
                  ▼
         ┌────────────────┐
         │  Core Parser   │
         │  (nom-based)   │
         └────────────────┘
                  │
      ┌───────────┼───────────┐
      ▼           ▼           ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│  Lexer  │ │Directive│ │ Clause  │
│         │ │ Parser  │ │ Parser  │
└─────────┘ └─────────┘ └─────────┘

Key Design Principles:

  • Safe by default - Rust’s ownership system prevents memory errors
  • Zero-copy parsing - Uses string slices, not allocations
  • Minimal unsafe - FFI boundary only, well-documented
  • Extensible - Easy to add new directives and clauses

OpenMP Support

ROUP currently supports OpenMP 5.0+ with comprehensive coverage:

Supported Directives (15+)

  • parallel - Parallel regions
  • for - Worksharing loops
  • sections, single - Worksharing constructs
  • task, taskwait, taskgroup - Tasking
  • target, teams, distribute - Device offloading
  • barrier, critical, atomic - Synchronization
  • metadirective - Dynamic selection
  • And more…

Supported Clauses (50+)

  • Data sharing: private, shared, firstprivate, lastprivate
  • Parallelism control: num_threads, if, proc_bind
  • Worksharing: schedule, collapse, nowait
  • Reductions: reduction with 10+ operators (+, *, min, max, etc.)
  • Device: map, device, defaultmap
  • Dependencies: depend, in, out, inout
  • And more…

See the OpenMP Support Matrix for the complete list.


Safety Guarantees

All unsafe code is isolated to the FFI boundary (src/c_api.rs), documented with safety requirements, and NULL-checked before dereferencing.


Getting Started

Want to experiment with ROUP? Check out our tutorials:

Or jump straight to the code:


License

ROUP is open source under the MIT License.

Copyright © 2024-2025 Anjia Wang

Getting Started

This guide shows how to compile ROUP, add the crate to a Rust project, and link the C/C++ bindings.

Prerequisites

  • Rust 1.88 or newer (rustup is recommended).
  • A C/C++ toolchain (clang or GCC) when using the FFI bindings.
  • Optional: a Fortran compiler for the example programs in examples/fortran/.

Clone and build the project:

git clone https://github.com/ouankou/roup.git
cd roup
cargo build --release

The build produces libroup.so on Linux, libroup.dylib on macOS, and roup.dll on Windows inside target/release/.

Rust quick start

Add ROUP to your Cargo.toml:

[dependencies]
roup = "0.7"

Example program:

use roup::parser::openmp;
use roup::lexer::Language;

fn main() {
    let input = "#pragma omp parallel for num_threads(4)";
    let parser = openmp::parser().with_language(Language::C);

    match parser.parse(input) {
        Ok((_, directive)) => {
            println!("directive: {}", directive.name);
            println!("clauses: {}", directive.clauses.len());
        }
        Err(err) => eprintln!("parse error: {err:?}"),
    }
}

Run it with cargo run.

C quick start

Write a small program that calls the C API:

#include <stdint.h>
#include <stdio.h>

struct OmpDirective;

struct OmpDirective* roup_parse(const char* input);
int32_t roup_directive_clause_count(const struct OmpDirective* dir);
void roup_directive_free(struct OmpDirective* dir);

int main(void) {
    struct OmpDirective* directive = roup_parse("#pragma omp parallel num_threads(4)");
    if (!directive) {
        fputs("parse failed\n", stderr);
        return 1;
    }

    printf("clause count: %d\n", roup_directive_clause_count(directive));
    roup_directive_free(directive);
    return 0;
}

Compile against the release build of ROUP:

cargo build --release
clang example.c \
  -L./target/release \
  -lroup -lpthread -ldl -lm \
  -Wl,-rpath,./target/release \
  -o example
./example

macOS users can replace the rpath with -Wl,-rpath,@executable_path/../target/release.

C++ quick start

The C API can be wrapped with RAII helpers:

#include <cstdint>
#include <iostream>

extern "C" {
struct OmpDirective;
OmpDirective* roup_parse(const char* input);
int32_t roup_directive_clause_count(const OmpDirective* dir);
void roup_directive_free(OmpDirective* dir);
}

class Directive {
public:
    explicit Directive(const char* input) : ptr_(roup_parse(input)) {}
    ~Directive() { if (ptr_) roup_directive_free(ptr_); }
    Directive(const Directive&) = delete;
    Directive& operator=(const Directive&) = delete;
    Directive(Directive&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }

    bool valid() const { return ptr_ != nullptr; }
    int32_t clause_count() const { return ptr_ ? roup_directive_clause_count(ptr_) : 0; }

private:
    OmpDirective* ptr_;
};

int main() {
    Directive directive("#pragma omp parallel for num_threads(4)");
    if (!directive.valid()) {
        std::cerr << "parse failed\n";
        return 1;
    }

    std::cout << "clauses: " << directive.clause_count() << "\n";
}

Compile with clang++ or g++ in the same way as the C example.

Next steps

  • Explore the complete examples in examples/ (C, C++, Fortran).
  • Read the Rust tutorial for more detailed use cases.
  • Consult the Testing guide before contributing changes.
  • Try the interactive debugger: cargo run --release --bin roup_debug '#pragma omp parallel' -- --non-interactive.

Building Guide

This guide covers building ROUP and integrating it into your projects across different languages and platforms.


Quick Start

# Clone the repository
git clone https://github.com/ouankou/roup.git
cd roup

# Build the library (release mode, optimized)
cargo build --release

# Run tests to verify
cargo test

# Build is complete! Library at: target/release/libroup.{a,so,dylib,dll}

Next steps:

  • Rust users: Add ROUP as a dependency (see below)
  • C users: Link against libroup.a (see C Tutorial)
  • C++ users: Use RAII wrappers (see C++ Tutorial)

Rust Integration

Add to your Cargo.toml:

[dependencies]
roup = "0.7"

Then use in your code:

use roup::parser::openmp;
use roup::lexer::Language;

fn main() {
    let parser = openmp::parser().with_language(Language::C);
    let (_, directive) = parser.parse("#pragma omp parallel for").unwrap();
    println!("Parsed directive: {}", directive.name);
}

Option 2: Using Git Dependency

[dependencies]
roup = { git = "https://github.com/ouankou/roup.git" }

Option 3: Local Development

[dependencies]
roup = { path = "../roup" }

See Rust Tutorial for complete usage examples and roup_debug for interactive inspection.


C Integration

Prerequisites

  • Rust toolchain (to build libroup)
  • C compiler: GCC, Clang, or MSVC
  • Build tools: Make or CMake (optional)

Step 1: Build the ROUP Library

cd /path/to/roup
cargo build --release

This creates:

  • Linux: target/release/libroup.{a,so}
  • macOS: target/release/libroup.{a,dylib}
  • Windows: target/release/roup.{lib,dll}

Step 2: Create Header File

Create roup_ffi.h with function declarations (see C Tutorial for complete header). The generated constants header lives at src/roup_constants.h after a build.

Step 3: Compile Your C Program

Using GCC/Clang (Linux/macOS)

# Static linking
gcc -o myapp main.c \
    -I/path/to/roup_ffi.h \
    -L/path/to/roup/target/release \
    -lroup \
    -lpthread -ldl -lm

# Dynamic linking with rpath
gcc -o myapp main.c \
    -I/path/to/roup_ffi.h \
    -L/path/to/roup/target/release \
    -lroup \
    -Wl,-rpath,/path/to/roup/target/release \
    -lpthread -ldl -lm

Using CMake

cmake_minimum_required(VERSION 3.10)
project(MyApp C)

# Add ROUP library
add_library(roup STATIC IMPORTED)
set_target_properties(roup PROPERTIES
    IMPORTED_LOCATION "/path/to/roup/target/release/libroup.a"
)

# Create executable
add_executable(myapp main.c)
target_include_directories(myapp PRIVATE "/path/to/roup_ffi.h")
target_link_libraries(myapp roup pthread dl m)

Using MSVC (Windows)

REM Build ROUP library first
cargo build --release

REM Compile C program
cl.exe main.c /I"C:\path\to\roup" ^
    /link "C:\path\to\roup\target\release\roup.lib" ^
    ws2_32.lib userenv.lib

Step 4: Run Your Program

# If using static linking
./myapp

# If using dynamic linking without rpath
LD_LIBRARY_PATH=/path/to/roup/target/release ./myapp

# Windows
set PATH=C:\path\to\roup\target\release;%PATH%
myapp.exe

Complete Example

See examples/c/tutorial_basic.c for a full working example with build instructions.


C++ Integration

C++ programs use the same C API with optional RAII wrappers for automatic memory management.

Step 1: Build ROUP Library

cargo build --release

Step 2: Create RAII Wrappers

See C++ Tutorial - Step 2 for complete wrapper code.

Step 3: Compile with C++17

# Using g++
g++ -o myapp main.cpp \
    -I/path/to/roup_ffi.h \
    -I/path/to/roup_wrapper.hpp \
    -L/path/to/roup/target/release \
    -lroup \
    -std=c++17 \
    -lpthread -ldl -lm

# Using Clang++
clang++ -o myapp main.cpp \
    -I/path/to/roup_ffi.h \
    -I/path/to/roup_wrapper.hpp \
    -L/path/to/roup/target/release \
    -lroup \
    -std=c++17 \
    -lpthread -ldl -lm

CMake for C++

cmake_minimum_required(VERSION 3.10)
project(MyApp CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_library(roup STATIC IMPORTED)
set_target_properties(roup PROPERTIES
    IMPORTED_LOCATION "/path/to/roup/target/release/libroup.a"
)

add_executable(myapp main.cpp)
target_include_directories(myapp PRIVATE 
    "/path/to/roup_ffi.h"
    "/path/to/roup_wrapper.hpp"
)
target_link_libraries(myapp roup pthread dl m)

Platform-Specific Notes

Linux

Ubuntu/Debian

# Install build tools
sudo apt-get update
sudo apt-get install build-essential curl git

# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Build ROUP
cargo build --release

Fedora/RHEL

# Install build tools
sudo dnf install gcc git

# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Build ROUP
cargo build --release

Library location: target/release/libroup.{a,so}

macOS

# Install Xcode Command Line Tools
xcode-select --install

# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Build ROUP
cargo build --release

Library location: target/release/libroup.{a,dylib}

Note: On macOS, the dynamic library extension is .dylib, not .so.

Windows

Using Rust with MSVC

# Install Rust (uses MSVC toolchain by default on Windows)
# Download from: https://rustup.rs/

# Install Visual Studio Build Tools
# Download from: https://visualstudio.microsoft.com/downloads/

# Build ROUP
cargo build --release

Library location: target\release\roup.{lib,dll}

Using Rust with GNU (MinGW)

# Install MSYS2 from https://www.msys2.org/
# Then in MSYS2 terminal:

# Install MinGW toolchain
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-rust

# Build ROUP
cargo build --release

Windows Subsystem for Linux provides a full Linux environment:

# In WSL (Ubuntu)
sudo apt-get install build-essential
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo build --release

Build Configurations

Debug Build (Development)

cargo build

# Output: target/debug/libroup.{a,so}
# Features: Debug symbols, assertions, slower but better errors

Release Build (Production)

cargo build --release

# Output: target/release/libroup.{a,so}
# Features: Optimized, no debug symbols, faster execution

Release with Debug Info

cargo build --release --config profile.release.debug=true

# Output: target/release/libroup.{a,so}
# Features: Optimized but with debug symbols for profiling

Custom Features

# Build with all features
cargo build --all-features

# Build with specific feature
cargo build --features "serde"

# Build without default features
cargo build --no-default-features

Testing

Run All Tests

# Run all tests
cargo test

# Run with output
cargo test -- --nocapture

# Run specific test
cargo test test_parallel_directive

# Run tests for specific module
cargo test parser::

Run Examples

Examples live under examples/ for C, C++, and Fortran. Build them with the provided Makefiles in those directories.


Troubleshooting

“linker cc not found”

Problem: No C compiler installed.

Solution:

# Linux
sudo apt-get install build-essential

# macOS
xcode-select --install

# Windows
# Install Visual Studio Build Tools

“cannot find -lroup”

Problem: Linker can’t find the ROUP library.

Solution:

# Verify library exists
ls -lh target/release/libroup.*

# Rebuild if needed
cargo build --release

# Check linker path
gcc ... -L$(pwd)/target/release -lroup

“error while loading shared libraries: libroup.so”

Problem: Runtime can’t find dynamic library.

Solution 1 - rpath:

gcc ... -Wl,-rpath,/path/to/roup/target/release

Solution 2 - LD_LIBRARY_PATH:

export LD_LIBRARY_PATH=/path/to/roup/target/release:$LD_LIBRARY_PATH
./myapp

Solution 3 - Install system-wide:

sudo cp target/release/libroup.so /usr/local/lib/
sudo ldconfig

Rust Version Too Old

Problem: Compilation fails with version error.

Solution:

# Update Rust toolchain
rustup update stable

# Verify version
rustc --version
# Ensure your Rust version is at least 1.88.0

Windows: “VCRUNTIME140.dll missing”

Problem: Missing Visual C++ runtime.

Solution: Download and install Visual C++ Redistributable


Build Performance Tips

Faster Incremental Builds

# Use sccache for caching
cargo install sccache
export RUSTC_WRAPPER=sccache

# Use faster linker (Linux)
sudo apt-get install lld
export RUSTFLAGS="-C link-arg=-fuse-ld=lld"

Parallel Builds

# Use all CPU cores (default)
cargo build -j $(nproc)

# Limit parallel jobs
cargo build -j 4

Reduce Binary Size

# Add to Cargo.toml
[profile.release]
opt-level = "z"     # Optimize for size
lto = true          # Link-time optimization
codegen-units = 1   # Better optimization
strip = true        # Remove debug symbols

Cross-Compilation

Linux to Windows

# Install target
rustup target add x86_64-pc-windows-gnu

# Install MinGW
sudo apt-get install mingw-w64

# Build
cargo build --release --target x86_64-pc-windows-gnu

macOS to Linux

# Install target
rustup target add x86_64-unknown-linux-gnu

# Build (requires cross-compilation setup)
cargo build --release --target x86_64-unknown-linux-gnu

For more complex cross-compilation, consider cross:

cargo install cross
cross build --release --target x86_64-unknown-linux-gnu

IDE Setup

Visual Studio Code

Recommended extensions:

  • rust-analyzer - Language server
  • CodeLLDB - Debugger
  • crates - Dependency management

CLion / IntelliJ IDEA

Install the Rust plugin from JetBrains marketplace.

Vim/Neovim

Use rust-analyzer with your LSP client (coc.nvim, nvim-lspconfig, etc.)


Next Steps

After building successfully:


Getting Help

  • Build issues: Check GitHub Issues
  • Questions: See FAQ
  • Examples: Browse examples/ directory
  • Documentation: Read docs/ directory

Rust Tutorial

This tutorial shows the current Rust entry points for parsing and inspecting OpenMP and OpenACC directives.


Basic Parsing

use roup::parser::openmp;
use roup::lexer::Language;

fn main() {
    let parser = openmp::parser().with_language(Language::C);
    let (_, directive) = parser
        .parse("#pragma omp parallel for num_threads(4)")
        .expect("parse");

    println!("name: {}", directive.name);
    println!("clauses: {}", directive.clauses.len());
}

OpenACC works the same way:

use roup::parser::openacc;
use roup::lexer::Language;

let parser = openacc::parser().with_language(Language::FortranFree);
let (_, directive) = parser.parse("!$acc parallel async(1)").unwrap();
println!("name: {}", directive.name);

Language controls sentinel parsing (C, FortranFree, FortranFixed). openmp::parse_omp_directive and openacc::parse_acc_directive provide convenience wrappers if you do not need to customise the parser.


Error Handling

use roup::parser::openmp;

fn parse_or_report(input: &str) {
    let parser = openmp::parser();
    match parser.parse(input) {
        Ok((rest, dir)) if rest.trim().is_empty() => {
            println!("parsed {}", dir.name);
        }
        Ok((rest, _)) => eprintln!("trailing tokens: {rest:?}"),
        Err(e) => eprintln!("parse error: {e:?}"),
    }
}

Working with Clauses

use roup::parser::openmp;

let parser = openmp::parser();
let (_, directive) = parser.parse("#pragma omp parallel private(x) reduction(+:sum)").unwrap();

for clause in &directive.clauses {
    match clause.name.as_ref() {
        "private" => println!("private({:?})", clause.kind),
        "reduction" => println!("reduction({:?})", clause.kind),
        other => println!("clause: {other}"),
    }
}

clause.kind preserves the original clause text (Parenthesized or Bare).


Building AST/IR

Use Parser::parse_ast to build the IR and normalise clauses:

use roup::ast::ClauseNormalizationMode;
use roup::ir::ParserConfig;
use roup::parser::openmp;

let parser = openmp::parser();
let ir = parser
    .parse_ast(
        "#pragma omp parallel for private(i,j)",
        ClauseNormalizationMode::ParserParity,
        &ParserConfig::default(),
    )
    .unwrap();
println!("{}", ir.to_string());

Translation API

Translate between C/C++ and Fortran OpenMP pragmas:

use roup::ir::translate::{translate_c_to_fortran, translate_fortran_to_c};

let f = translate_c_to_fortran("#pragma omp parallel for private(i)")?;
assert_eq!(f, "!$omp parallel do private(i)");

let c = translate_fortran_to_c("!$omp target teams distribute")?;
assert_eq!(c, "#pragma omp target teams distribute");

Debugger

roup_debug (built with the crate) provides interactive and batch step tracing:

cargo run --release --bin roup_debug '#pragma omp parallel' -- --non-interactive

It supports OpenMP and OpenACC pragmas in C and Fortran forms.

C Tutorial

This tutorial demonstrates how to use ROUP’s minimal unsafe pointer-based C API for parsing OpenMP directives. The API uses direct C pointers following standard malloc/free patterns familiar to C programmers.

API Design: Direct pointers (*mut OmpDirective, *mut OmpClause) with manual memory management. No global state, no handles.

Source: src/c_api.rs - minimal unsafe code confined to the FFI boundary


Prerequisites

Before starting, ensure you have:

  • C compiler (GCC, Clang, or MSVC)
  • ROUP library compiled (see Building Guide)
  • Basic understanding of malloc/free patterns

Example code: See examples/c/tutorial_basic.c for a complete working example.


Step 1: Setup and Compilation

Project Structure

my-project/
├── src/
│   └── main.c
├── include/
│   └── roup_ffi.h      # Forward declarations
└── libroup.a            # Built from cargo build

Forward Declarations

Create include/roup_ffi.h with the C API declarations:

#ifndef ROUP_FFI_H
#define ROUP_FFI_H

#include <stdint.h>

// Opaque types (defined in Rust)
typedef struct OmpDirective OmpDirective;
typedef struct OmpClause OmpClause;
typedef struct OmpClauseIterator OmpClauseIterator;
typedef struct OmpStringList OmpStringList;

// Lifecycle functions
extern OmpDirective* roup_parse(const char* input);
extern void roup_directive_free(OmpDirective* directive);
extern void roup_clause_free(OmpClause* clause);

// Directive queries
extern int32_t roup_directive_kind(const OmpDirective* directive);
extern int32_t roup_directive_clause_count(const OmpDirective* directive);
extern OmpClauseIterator* roup_directive_clauses_iter(const OmpDirective* directive);

// Iterator functions
extern int32_t roup_clause_iterator_next(OmpClauseIterator* iter, OmpClause** out);
extern void roup_clause_iterator_free(OmpClauseIterator* iter);

// Clause queries
extern int32_t roup_clause_kind(const OmpClause* clause);
extern int32_t roup_clause_schedule_kind(const OmpClause* clause);
extern int32_t roup_clause_reduction_operator(const OmpClause* clause);
extern int32_t roup_clause_default_data_sharing(const OmpClause* clause);

// Variable lists
extern OmpStringList* roup_clause_variables(const OmpClause* clause);
extern int32_t roup_string_list_len(const OmpStringList* list);
extern const char* roup_string_list_get(const OmpStringList* list, int32_t index);
extern void roup_string_list_free(OmpStringList* list);

#endif // ROUP_FFI_H

Compilation

Option 1: Using GCC/Clang

# Build ROUP library
cargo build --release

# Compile C program
gcc -o my_app src/main.c \
    -I include \
    -L target/release \
    -lroup \
    -lpthread -ldl -lm

Option 2: Using CMake

cmake_minimum_required(VERSION 3.10)
project(roup_example C)

add_executable(my_app src/main.c)
target_include_directories(my_app PRIVATE include)
target_link_libraries(my_app ${CMAKE_SOURCE_DIR}/target/release/libroup.a pthread dl m)

Step 2: Parse a Simple Directive

Let’s start with the most basic operation: parsing a simple directive.

#include <stdio.h>
#include "roup_ffi.h"

int main(void) {
    const char* input = "#pragma omp parallel";
    
    // Parse the directive
    OmpDirective* dir = roup_parse(input);
    
    // Check for errors (NULL = parse failed)
    if (!dir) {
        fprintf(stderr, "Parse failed!\n");
        return 1;
    }
    
    printf("✅ Parse succeeded!\n");
    
    // IMPORTANT: Free the directive
    roup_directive_free(dir);
    
    return 0;
}

Key Points:

  • roup_parse() returns a pointer or NULL on error
  • Always check for NULL before using the directive
  • Always call roup_directive_free() to prevent memory leaks

Step 3: Query Directive Properties

After parsing, you can query the directive’s properties:

#include <stdio.h>
#include "roup_ffi.h"

int main(void) {
    const char* input = "#pragma omp parallel for num_threads(4)";
    
    OmpDirective* dir = roup_parse(input);
    if (!dir) {
        return 1;
    }
    
    // Query directive properties
    int32_t kind = roup_directive_kind(dir);
    int32_t clause_count = roup_directive_clause_count(dir);
    
    printf("Directive kind: %d\n", kind);
    printf("Clause count: %d\n", clause_count);
    
    roup_directive_free(dir);
    return 0;
}

Output:

Directive kind: 28
Clause count: 1

Note: Directive kind is an integer from the parser’s internal registry. For practical use, you typically care more about the clauses than the exact directive kind. The kind value comes from the order in which directives were registered in src/parser/openmp.rs - these internal IDs are not part of the stable API and may change between versions.


Step 4: Iterate Through Clauses

To access individual clauses, use the iterator pattern:

#include <stdio.h>
#include "roup_ffi.h"

const char* clause_name(int32_t kind) {
    switch(kind) {
        case 0: return "num_threads";
        case 1: return "if";
        case 2: return "private";
        case 3: return "shared";
        case 6: return "reduction";
        case 7: return "schedule";
        case 10: return "nowait";
        default: return "unknown";
    }
}

int main(void) {
    const char* input = "#pragma omp parallel num_threads(8) default(shared) nowait";
    
    OmpDirective* dir = roup_parse(input);
    if (!dir) return 1;
    
    // Create iterator
    OmpClauseIterator* iter = roup_directive_clauses_iter(dir);
    if (!iter) {
        roup_directive_free(dir);
        return 1;
    }
    
    // Iterate through clauses
    printf("Clauses:\n");
    OmpClause* clause;
    while (roup_clause_iterator_next(iter, &clause)) {
        int32_t kind = roup_clause_kind(clause);
        printf("  - %s (kind=%d)\n", clause_name(kind), kind);
    }
    
    // Cleanup
    roup_clause_iterator_free(iter);
    roup_directive_free(dir);
    
    return 0;
}

Output:

Clauses:
  - num_threads (kind=0)
  - default (kind=11)
  - nowait (kind=10)

Key Points:

  • roup_clause_iterator_next() returns 1 if clause available, 0 when done
  • Write the clause pointer to out parameter
  • Always free the iterator with roup_clause_iterator_free()

Step 5: Query Clause-Specific Data

Different clause types have different data. Use type-specific query functions:

Schedule Clause

OmpClause* clause = /* ... get clause ... */;
if (roup_clause_kind(clause) == 7) {  // SCHEDULE
    int32_t sched = roup_clause_schedule_kind(clause);
    const char* names[] = {"static", "dynamic", "guided", "auto", "runtime"};
    printf("Schedule: %s\n", names[sched]);
}

Reduction Clause

if (roup_clause_kind(clause) == 6) {  // REDUCTION
    int32_t op = roup_clause_reduction_operator(clause);
    const char* ops[] = {"+", "-", "*", "&", "|", "^", "&&", "||", "min", "max"};
    printf("Reduction operator: %s\n", ops[op]);
}

Default Clause

if (roup_clause_kind(clause) == 11) {  // DEFAULT
    int32_t def = roup_clause_default_data_sharing(clause);
    printf("Default: %s\n", def == 0 ? "shared" : "none");
}

Complete Example

#include <stdio.h>
#include "roup_ffi.h"

int main(void) {
    const char* input = "#pragma omp parallel for schedule(static, 10) reduction(+:sum)";
    
    OmpDirective* dir = roup_parse(input);
    if (!dir) return 1;
    
    OmpClauseIterator* iter = roup_directive_clauses_iter(dir);
    if (!iter) {
        roup_directive_free(dir);
        return 1;
    }
    
    OmpClause* clause;
    while (roup_clause_iterator_next(iter, &clause)) {
        int32_t kind = roup_clause_kind(clause);
        
        if (kind == 7) {  // SCHEDULE
            int32_t sched = roup_clause_schedule_kind(clause);
            const char* names[] = {"static", "dynamic", "guided", "auto", "runtime"};
            printf("Schedule: %s\n", names[sched]);
        }
        else if (kind == 6) {  // REDUCTION
            int32_t op = roup_clause_reduction_operator(clause);
            const char* ops[] = {"+", "-", "*", "&", "|", "^", "&&", "||", "min", "max"};
            printf("Reduction: %s\n", ops[op]);
        }
    }
    
    roup_clause_iterator_free(iter);
    roup_directive_free(dir);
    
    return 0;
}

Output:

Schedule: static
Reduction: +

Step 6: Access Variable Lists

Clauses like private(x, y, z) contain lists of variables:

#include <stdio.h>
#include "roup_ffi.h"

int main(void) {
    const char* input = "#pragma omp parallel private(x, y, z) shared(a, b)";
    
    OmpDirective* dir = roup_parse(input);
    if (!dir) return 1;
    
    OmpClauseIterator* iter = roup_directive_clauses_iter(dir);
    if (!iter) {
        roup_directive_free(dir);
        return 1;
    }
    
    OmpClause* clause;
    while (roup_clause_iterator_next(iter, &clause)) {
        int32_t kind = roup_clause_kind(clause);
        
        // Get variable list
        OmpStringList* vars = roup_clause_variables(clause);
        if (vars) {
            int32_t len = roup_string_list_len(vars);
            
            const char* kind_name = (kind == 2) ? "private" : "shared";
            printf("%s variables: ", kind_name);
            
            for (int32_t i = 0; i < len; i++) {
                const char* var = roup_string_list_get(vars, i);
                printf("%s%s", var, (i < len - 1) ? ", " : "");
            }
            printf("\n");
            
            roup_string_list_free(vars);
        }
    }
    
    roup_clause_iterator_free(iter);
    roup_directive_free(dir);
    
    return 0;
}

Output:

private variables: x, y, z
shared variables: a, b

Key Points:

  • roup_clause_variables() returns a OmpStringList* or NULL
  • Use roup_string_list_len() to get the count
  • Use roup_string_list_get(list, index) to access individual strings
  • Always call roup_string_list_free() when done

Step 7: Error Handling

Robust error handling is crucial. The API returns NULL on failure:

#include <stdio.h>
#include "roup_ffi.h"

int main(void) {
    // Test 1: Invalid syntax
    const char* invalid = "#pragma omp INVALID_DIRECTIVE";
    OmpDirective* dir1 = roup_parse(invalid);
    if (!dir1) {
        printf("✓ Invalid syntax correctly rejected\n");
    }
    
    // Test 2: NULL input
    OmpDirective* dir2 = roup_parse(NULL);
    if (!dir2) {
        printf("✓ NULL input correctly rejected\n");
    }
    
    // Test 3: Empty string
    OmpDirective* dir3 = roup_parse("");
    if (!dir3) {
        printf("✓ Empty string correctly rejected\n");
    }
    
    // Test 4: Querying NULL
    int32_t kind = roup_directive_kind(NULL);
    printf("roup_directive_kind(NULL) = %d\n", kind);  // Returns -1
    
    return 0;
}

Error Handling Guidelines:

  1. Always check roup_parse() return value for NULL
  2. Check roup_directive_clauses_iter() for NULL
  3. Query functions return -1 or safe defaults for NULL inputs
  4. Free resources even in error paths (if allocated)

Step 8: Complete Example

Here’s a complete program that demonstrates all concepts:

#include <stdio.h>
#include <stdlib.h>
#include "roup_ffi.h"

void print_clause_details(OmpClause* clause) {
    int32_t kind = roup_clause_kind(clause);
    
    switch(kind) {
        case 0:
            printf("  - num_threads\n");
            break;
        case 2: {
            printf("  - private(");
            OmpStringList* vars = roup_clause_variables(clause);
            if (vars) {
                int32_t len = roup_string_list_len(vars);
                for (int32_t i = 0; i < len; i++) {
                    printf("%s%s", roup_string_list_get(vars, i), 
                           (i < len - 1) ? ", " : "");
                }
                roup_string_list_free(vars);
            }
            printf(")\n");
            break;
        }
        case 6: {
            printf("  - reduction(");
            int32_t op = roup_clause_reduction_operator(clause);
            const char* ops[] = {"+", "-", "*", "&", "|", "^", "&&", "||", "min", "max"};
            if (op >= 0 && op < 10) {
                printf("%s", ops[op]);
            }
            printf(":...)\n");
            break;
        }
        case 7: {
            printf("  - schedule(");
            int32_t sched = roup_clause_schedule_kind(clause);
            const char* names[] = {"static", "dynamic", "guided", "auto", "runtime"};
            if (sched >= 0 && sched < 5) {
                printf("%s", names[sched]);
            }
            printf(")\n");
            break;
        }
        case 10:
            printf("  - nowait\n");
            break;
        case 11:
            printf("  - default(%s)\n", 
                   roup_clause_default_data_sharing(clause) == 0 ? "shared" : "none");
            break;
        default:
            printf("  - unknown (kind=%d)\n", kind);
            break;
    }
}

int main(void) {
    const char* test_cases[] = {
        "#pragma omp parallel",
        "#pragma omp parallel for num_threads(4) private(i, j)",
        "#pragma omp parallel for schedule(static, 100) reduction(+:sum)",
        "#pragma omp task default(shared) nowait",
        NULL
    };
    
    for (int i = 0; test_cases[i] != NULL; i++) {
        printf("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
        printf("Input: %s\n", test_cases[i]);
        printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
        
        OmpDirective* dir = roup_parse(test_cases[i]);
        if (!dir) {
            printf("❌ Parse failed!\n");
            continue;
        }
        
        int32_t clause_count = roup_directive_clause_count(dir);
        printf("Clauses: %d\n", clause_count);
        
        if (clause_count > 0) {
            OmpClauseIterator* iter = roup_directive_clauses_iter(dir);
            if (iter) {
                OmpClause* clause;
                while (roup_clause_iterator_next(iter, &clause)) {
                    print_clause_details(clause);
                }
                roup_clause_iterator_free(iter);
            }
        }
        
        roup_directive_free(dir);
    }
    
    printf("\n✅ All tests completed!\n\n");
    return 0;
}

Clause Kind Reference

The C API supports 12 common clause types:

KindClauseHas VariablesHas Specific Data
0num_threadsNoValue (int)
1ifNoCondition (string)
2privateYesVariable list
3sharedYesVariable list
4firstprivateYesVariable list
5lastprivateYesVariable list
6reductionYesOperator + variables
7scheduleNoSchedule kind + chunk
8collapseNoDepth (int)
9orderedNo-
10nowaitNo-
11defaultNoSharing kind
-1Unknown--

Schedule Kinds (for clause kind 7):

  • 0 = static
  • 1 = dynamic
  • 2 = guided
  • 3 = auto
  • 4 = runtime

Reduction Operators (for clause kind 6):

  • 0 = +, 1 = -, 2 = *
  • 3 = &, 4 = |, 5 = ^
  • 6 = &&, 7 = ||
  • 8 = min, 9 = max

Default Kinds (for clause kind 11):

  • 0 = shared
  • 1 = none
  • 2 = private
  • 3 = firstprivate

Memory Management Checklist

DO:

  • Call roup_directive_free() for every successful roup_parse()
  • Call roup_clause_iterator_free() for every roup_directive_clauses_iter()
  • Call roup_string_list_free() for every roup_clause_variables()
  • Check for NULL returns before using pointers

DON’T:

  • Call roup_clause_free() on clauses from iterators (owned by directive)
  • Access freed pointers (use-after-free)
  • Forget to free in error paths
  • Assume parse always succeeds

Performance Tips

  1. Reuse parsed directives - Don’t reparse the same string repeatedly
  2. Minimize FFI crossings - Batch operations when possible
  3. Avoid unnecessary iteration - If you only need clause count, don’t iterate
  4. Use local variables - Cache query results instead of calling repeatedly

Example (inefficient):

// BAD: Queries kind multiple times
for (int i = 0; i < count; i++) {
    if (roup_clause_kind(clause) == 2) {
        process_private(roup_clause_kind(clause));
    }
}

Example (efficient):

// GOOD: Cache the kind
int32_t kind = roup_clause_kind(clause);
if (kind == 2) {
    process_private(kind);
}

Next Steps

Now that you understand the C API basics:

  1. Build the example - Compile and run examples/c/tutorial_basic.c
  2. Explore directives - See OpenMP Support for all 120+ directives
  3. Advanced usage - Check API Reference for complete function details
  4. C++ wrappers - Read C++ Tutorial for RAII wrappers

Full Example Code: examples/c/tutorial_basic.c (433 lines with detailed comments)


Troubleshooting

Linker Errors

Problem: undefined reference to roup_parse

Solution: Link against the ROUP static library:

gcc ... -L target/release -lroup -lpthread -ldl -lm

Parse Always Returns NULL

Problem: All parses fail, even valid input

Solution:

  • Check that the library was built correctly (cargo build --release)
  • Verify the input string is valid OpenMP syntax
  • Ensure the string is null-terminated
  • Try the examples first to verify the library works

Memory Leaks

Problem: Valgrind reports memory leaks

Solution:

  • Ensure every roup_parse() has a matching roup_directive_free()
  • Ensure every roup_directive_clauses_iter() has a matching roup_clause_iterator_free()
  • Ensure every roup_clause_variables() has a matching roup_string_list_free()

Segmentation Fault

Problem: Program crashes with segfault

Solution:

  • Check for NULL before dereferencing pointers
  • Don’t access freed pointers
  • Don’t call roup_clause_free() on clauses from iterators

Questions? Check the FAQ or open an issue on GitHub.

C++ Tutorial: Building a Real Application

This tutorial shows how to integrate ROUP into a real C++ application using modern C++17 features.

We’ll build an OpenMP pragma analyzer - a tool that reads C/C++ source files, extracts OpenMP directives, and reports statistics.


What You’ll Build

A command-line tool that:

  1. Reads source files line-by-line
  2. Detects OpenMP pragmas
  3. Parses them using ROUP
  4. Reports directive types and clause counts
  5. Provides summary statistics

Example output:

$ ./omp_analyzer mycode.c
Found 5 OpenMP directives:
  Line 10: parallel (3 clauses)
  Line 25: for (2 clauses)
  Line 42: parallel for (4 clauses)
  Line 68: task (1 clause)
  Line 95: barrier (0 clauses)

Summary:
  Total directives: 5
  Total clauses: 10
  Most common: parallel (2 occurrences)

Prerequisites

  • C++ Compiler: clang++ or g++ with C++17 support
  • ROUP Library: Built and installed (see below)
  • System: Linux, macOS, or Windows with WSL

Building ROUP

git clone https://github.com/ouankou/roup.git
cd roup
cargo build --release

# Library is now at: target/release/libroup.so (Linux)
#                or: target/release/libroup.dylib (macOS)

Step 1: Understanding the ROUP C API

ROUP exports a minimal C API with 16 functions. Here are the key ones for our tool:

Lifecycle Functions

// Parse an OpenMP directive string
OmpDirective* roup_parse(const char* input);

// Free the parsed directive
void roup_directive_free(OmpDirective* directive);

Query Functions

// Get directive type (0=parallel, 1=for, 4=task, etc.)
int32_t roup_directive_kind(const OmpDirective* directive);

// Get number of clauses
int32_t roup_directive_clause_count(const OmpDirective* directive);

// Create iterator for clauses
OmpClauseIterator* roup_directive_clauses_iter(const OmpDirective* directive);

// Get next clause (returns 1 if available, 0 if done)
int32_t roup_clause_iterator_next(OmpClauseIterator* iter, OmpClause** out);

// Get clause type (0=num_threads, 2=private, 6=reduction, etc.)
int32_t roup_clause_kind(const OmpClause* clause);

// Free iterator
void roup_clause_iterator_free(OmpClauseIterator* iter);

Step 2: Create RAII Wrappers (Modern C++)

Instead of manual memory management, let’s use RAII (Resource Acquisition Is Initialization) to automatically clean up resources.

Create roup_wrapper.hpp:

#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <optional>

// Forward declarations of opaque C types
struct OmpDirective;
struct OmpClause;
struct OmpClauseIterator;

// C API declarations
extern "C" {
    OmpDirective* roup_parse(const char* input);
    void roup_directive_free(OmpDirective* directive);
    int32_t roup_directive_kind(const OmpDirective* directive);
    int32_t roup_directive_clause_count(const OmpDirective* directive);
    OmpClauseIterator* roup_directive_clauses_iter(const OmpDirective* directive);
    int32_t roup_clause_iterator_next(OmpClauseIterator* iter, OmpClause** out);
    void roup_clause_iterator_free(OmpClauseIterator* iter);
    int32_t roup_clause_kind(const OmpClause* clause);
}

namespace roup {

// RAII wrapper for OmpDirective
class Directive {
private:
    OmpDirective* ptr_;

public:
    // Constructor: parse directive
    explicit Directive(const std::string& input) 
        : ptr_(roup_parse(input.c_str())) {}
    
    // Destructor: automatic cleanup
    ~Directive() {
        if (ptr_) {
            roup_directive_free(ptr_);
        }
    }
    
    // Delete copy (move-only type)
    Directive(const Directive&) = delete;
    Directive& operator=(const Directive&) = delete;
    
    // Move constructor
    Directive(Directive&& other) noexcept : ptr_(other.ptr_) {
        other.ptr_ = nullptr;
    }
    
    // Move assignment
    Directive& operator=(Directive&& other) noexcept {
        if (this != &other) {
            if (ptr_) roup_directive_free(ptr_);
            ptr_ = other.ptr_;
            other.ptr_ = nullptr;
        }
        return *this;
    }
    
    // Check if parse succeeded
    bool valid() const { return ptr_ != nullptr; }
    explicit operator bool() const { return valid(); }
    
    // Get directive kind
    int32_t kind() const {
        return ptr_ ? roup_directive_kind(ptr_) : -1;
    }
    
    // Get clause count
    int32_t clause_count() const {
        return ptr_ ? roup_directive_clause_count(ptr_) : 0;
    }
    
    // Get raw pointer (for advanced usage)
    OmpDirective* get() const { return ptr_; }
};

// RAII wrapper for OmpClauseIterator
class ClauseIterator {
private:
    OmpClauseIterator* iter_;
    
public:
    explicit ClauseIterator(const Directive& directive)
        : iter_(directive.valid() ? roup_directive_clauses_iter(directive.get()) : nullptr) {}
    
    ~ClauseIterator() {
        if (iter_) {
            roup_clause_iterator_free(iter_);
        }
    }
    
    // Delete copy
    ClauseIterator(const ClauseIterator&) = delete;
    ClauseIterator& operator=(const ClauseIterator&) = delete;
    
    // Get next clause kind (returns std::optional)
    std::optional<int32_t> next() {
        if (!iter_) return std::nullopt;
        
        OmpClause* clause = nullptr;
        if (roup_clause_iterator_next(iter_, &clause) == 1) {
            return roup_clause_kind(clause);
            // Note: Don't free clause - owned by directive
        }
        return std::nullopt;
    }
};

// Helper: Convert directive kind to name
inline const char* directive_kind_name(int32_t kind) {
    switch (kind) {
        case 0: return "parallel";
        case 1: return "for";
        case 2: return "sections";
        case 3: return "single";
        case 4: return "task";
        case 5: return "master";
        case 6: return "critical";
        case 7: return "barrier";
        case 8: return "taskwait";
        case 9: return "taskgroup";
        case 10: return "atomic";
        case 11: return "flush";
        case 12: return "ordered";
        case 13: return "target";
        case 14: return "teams";
        case 15: return "distribute";
        case 16: return "metadirective";
        default: return "unknown";
    }
}

// Helper: Convert clause kind to name
inline const char* clause_kind_name(int32_t kind) {
    switch (kind) {
        case 0: return "num_threads";
        case 1: return "if";
        case 2: return "private";
        case 3: return "shared";
        case 4: return "firstprivate";
        case 5: return "lastprivate";
        case 6: return "reduction";
        case 7: return "schedule";
        case 8: return "collapse";
        case 9: return "ordered";
        case 10: return "nowait";
        case 11: return "default";
        default: return "unknown";
    }
}

} // namespace roup

Key RAII benefits:

  • Automatic cleanup - No need to call _free() functions
  • Exception safe - Resources freed even if exceptions thrown
  • Move semantics - Efficient transfer of ownership
  • Modern C++ - Uses std::optional, deleted copy constructors

Step 3: Build the Analyzer Tool

Create omp_analyzer.cpp:

#include "roup_wrapper.hpp"
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
#include <algorithm>

struct DirectiveInfo {
    int line_number;
    std::string directive_name;
    int clause_count;
    std::vector<std::string> clause_names;
};

class OMPAnalyzer {
private:
    std::vector<DirectiveInfo> directives_;
    std::map<std::string, int> directive_counts_;
    
public:
    // Analyze a single line for OpenMP pragmas
    void analyze_line(const std::string& line, int line_number) {
        // Check if line contains OpenMP pragma
        if (line.find("#pragma omp") == std::string::npos) {
            return;
        }
        
        // Parse the directive
        roup::Directive directive(line);
        if (!directive) {
            std::cerr << "Warning: Failed to parse line " << line_number 
                      << ": " << line << std::endl;
            return;
        }
        
        // Extract directive info
        DirectiveInfo info;
        info.line_number = line_number;
        info.directive_name = roup::directive_kind_name(directive.kind());
        info.clause_count = directive.clause_count();
        
        // Extract clause names
        roup::ClauseIterator iter(directive);
        while (auto clause_kind = iter.next()) {
            info.clause_names.push_back(roup::clause_kind_name(*clause_kind));
        }
        
        directives_.push_back(info);
        directive_counts_[info.directive_name]++;
    }
    
    // Analyze entire file
    bool analyze_file(const std::string& filename) {
        std::ifstream file(filename);
        if (!file) {
            std::cerr << "Error: Cannot open file: " << filename << std::endl;
            return false;
        }
        
        std::string line;
        int line_number = 0;
        
        while (std::getline(file, line)) {
            line_number++;
            analyze_line(line, line_number);
        }
        
        return true;
    }
    
    // Print detailed report
    void print_report() const {
        if (directives_.empty()) {
            std::cout << "No OpenMP directives found." << std::endl;
            return;
        }
        
        std::cout << "\nFound " << directives_.size() 
                  << " OpenMP directive(s):\n" << std::endl;
        
        for (const auto& info : directives_) {
            std::cout << "  Line " << info.line_number << ": "
                      << info.directive_name << " ("
                      << info.clause_count << " clause"
                      << (info.clause_count != 1 ? "s" : "") << ")";
            
            if (!info.clause_names.empty()) {
                std::cout << " [";
                for (size_t i = 0; i < info.clause_names.size(); ++i) {
                    if (i > 0) std::cout << ", ";
                    std::cout << info.clause_names[i];
                }
                std::cout << "]";
            }
            std::cout << std::endl;
        }
        
        print_summary();
    }
    
    // Print summary statistics
    void print_summary() const {
        int total_clauses = 0;
        for (const auto& info : directives_) {
            total_clauses += info.clause_count;
        }
        
        std::cout << "\nSummary:" << std::endl;
        std::cout << "  Total directives: " << directives_.size() << std::endl;
        std::cout << "  Total clauses: " << total_clauses << std::endl;
        
        // Find most common directive
        auto max_elem = std::max_element(
            directive_counts_.begin(), 
            directive_counts_.end(),
            [](const auto& a, const auto& b) { return a.second < b.second; }
        );
        
        if (max_elem != directive_counts_.end()) {
            std::cout << "  Most common: " << max_elem->first 
                      << " (" << max_elem->second << " occurrence"
                      << (max_elem->second != 1 ? "s" : "") << ")" << std::endl;
        }
        
        // Print directive type breakdown
        if (directive_counts_.size() > 1) {
            std::cout << "\nDirective breakdown:" << std::endl;
            for (const auto& [name, count] : directive_counts_) {
                std::cout << "  " << name << ": " << count << std::endl;
            }
        }
    }
};

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cerr << "Usage: " << argv[0] << " <source-file>" << std::endl;
        std::cerr << "Example: " << argv[0] << " mycode.c" << std::endl;
        return 1;
    }
    
    OMPAnalyzer analyzer;
    
    if (!analyzer.analyze_file(argv[1])) {
        return 1;
    }
    
    analyzer.print_report();
    return 0;
}
```text

---

## Step 4: Build and Run

### Compilation

```bash
# Build ROUP library first
cd roup
cargo build --release

# Build the analyzer
clang++ -std=c++17 omp_analyzer.cpp \
    -L./target/release -lroup \
    -Wl,-rpath,./target/release \
    -o omp_analyzer

# Or with g++:
g++ -std=c++17 omp_analyzer.cpp \
    -L./target/release -lroup \
    -Wl,-rpath,./target/release \
    -o omp_analyzer

Test with Sample File

Create test.c:

#include <stdio.h>

int main() {
    int n = 1000;
    int sum = 0;
    
    #pragma omp parallel for reduction(+:sum) num_threads(4)
    for (int i = 0; i < n; i++) {
        sum += i;
    }
    
    #pragma omp parallel
    {
        #pragma omp single
        {
            printf("Hello from thread\\n");
        }
    }
    
    #pragma omp task depend(in: sum)
    printf("Sum: %d\\n", sum);
    
    #pragma omp barrier
    
    return 0;
}

Run the Analyzer

$ ./omp_analyzer test.c

Found 5 OpenMP directive(s):

  Line 7: for (3 clauses) [reduction, num_threads]
  Line 11: parallel (0 clauses)
  Line 13: single (0 clauses)
  Line 19: task (1 clause) [depend]
  Line 22: barrier (0 clauses)

Summary:
  Total directives: 5
  Total clauses: 4
  Most common: parallel (1 occurrence)

Directive breakdown:
  barrier: 1
  for: 1
  parallel: 1
  single: 1
  task: 1

Step 5: Advanced Features

5.1 Extract Variable Names from Clauses

Some clauses (like private, shared) contain variable lists. To access them:

// In C API (add to roup_wrapper.hpp):
extern "C" {
    OmpStringList* roup_clause_variables(const OmpClause* clause);
    int32_t roup_string_list_len(const OmpStringList* list);
    const char* roup_string_list_get(const OmpStringList* list, int32_t index);
    void roup_string_list_free(OmpStringList* list);
}

// RAII wrapper for string list
class StringList {
private:
    OmpStringList* list_;
    
public:
    explicit StringList(OmpStringList* list) : list_(list) {}
    
    ~StringList() {
        if (list_) roup_string_list_free(list_);
    }
    
    int32_t size() const {
        return list_ ? roup_string_list_len(list_) : 0;
    }
    
    std::string get(int32_t index) const {
        if (!list_ || index >= size()) return "";
        return roup_string_list_get(list_, index);
    }
    
    std::vector<std::string> to_vector() const {
        std::vector<std::string> result;
        for (int32_t i = 0; i < size(); ++i) {
            result.push_back(get(i));
        }
        return result;
    }
};
```text

### 5.2 Handle Parse Errors Gracefully

```cpp
std::optional<DirectiveInfo> parse_directive(const std::string& line) {
    roup::Directive directive(line);
    if (!directive) {
        return std::nullopt;  // Parse failed
    }
    
    DirectiveInfo info;
    info.directive_name = roup::directive_kind_name(directive.kind());
    info.clause_count = directive.clause_count();
    
    return info;
}

5.3 Process Multiple Files

void analyze_project(const std::vector<std::string>& files) {
    OMPAnalyzer combined;
    
    for (const auto& file : files) {
        OMPAnalyzer file_analyzer;
        if (file_analyzer.analyze_file(file)) {
            std::cout << "\n=== " << file << " ===" << std::endl;
            file_analyzer.print_report();
        }
    }
}

Real-World Use Cases

1. OpenMP Linter

Check for common mistakes:

  • parallel for without private on loop variable
  • reduction with unsupported operators
  • Missing nowait opportunities

2. Code Modernization Tool

  • Convert OpenMP 3.x → 5.x syntax
  • Suggest modern alternatives (e.g., taskloop instead of manual tasks)

3. Performance Analyzer

  • Count parallelization opportunities
  • Identify nested parallel regions (potential over-subscription)
  • Find synchronization hotspots

4. IDE Integration

  • Syntax highlighting for OpenMP pragmas
  • Auto-completion for clause names
  • Quick documentation lookup

Performance Considerations

ROUP is fast:

  • Parsing a typical directive: ~1-5 microseconds
  • Zero-copy design: Uses string slices, not allocations
  • Suitable for real-time IDE integration

Benchmark (on typical code):

File size: 10,000 lines
OpenMP directives: 500
Total parse time: ~2.5 milliseconds
Throughput: ~200,000 directives/second

Troubleshooting

Issue: undefined reference to roup_parse

Solution: Make sure library path is correct:

clang++ ... -L./target/release -lroup -Wl,-rpath,./target/release

Issue: error while loading shared libraries: libroup.so

Solution: Set LD_LIBRARY_PATH (Linux) or DYLD_LIBRARY_PATH (macOS):

export LD_LIBRARY_PATH=./target/release:$LD_LIBRARY_PATH

Issue: Parse failures on valid pragmas

Cause: ROUP currently supports C/C++ syntax (#pragma omp), not Fortran (!$omp).

Solution: Ensure input starts with #pragma omp.


Next Steps


Complete Example Code

All code from this tutorial is available at:

  • examples/cpp/roup_wrapper.hpp - RAII wrappers
  • examples/cpp/omp_analyzer.cpp - Full analyzer tool

Clone and try:

git clone https://github.com/ouankou/roup.git
cd roup/examples/cpp
make  # Builds all examples
./omp_analyzer ../../tests/data/sample.c

Happy parsing! 🚀

Fortran Tutorial

This tutorial demonstrates how to use ROUP to parse OpenMP directives in Fortran code.

🚧 Experimental Status

Fortran support in ROUP is currently experimental. Features may change as development progresses.

Introduction

ROUP supports parsing OpenMP directives from Fortran source code in both free-form and fixed-form formats. This enables Fortran developers to:

  • Parse OpenMP directives from Fortran source
  • Validate directive syntax
  • Extract clause information
  • Build tools for Fortran+OpenMP code analysis

Fortran Directive Formats

Free-Form (Modern Fortran)

Free-form Fortran uses the !$OMP sentinel (case-insensitive):

!$OMP PARALLEL PRIVATE(A, B) NUM_THREADS(4)
!$OMP END PARALLEL

!$omp parallel do private(i) reduction(+:sum)
!$OMP END PARALLEL DO

Fixed-Form (Fortran 77)

Fixed-form Fortran uses !$OMP or C$OMP sentinels in columns 1-6:

C$OMP PARALLEL PRIVATE(X, Y)
!$OMP DO SCHEDULE(STATIC, 10)
C$OMP END DO
C$OMP END PARALLEL

Fortran-Specific Directives

ROUP supports Fortran-specific directive syntax, including the Fortran DO construct which is equivalent to C/C++ FOR:

DO Directive (Fortran)

!$OMP DO PRIVATE(I) SCHEDULE(STATIC)
    do i = 1, n
        ! Loop body
    end do
!$OMP END DO

PARALLEL DO (Fortran)

!$OMP PARALLEL DO PRIVATE(I) REDUCTION(+:SUM)
    do i = 1, n
        sum = sum + a(i)
    end do
!$OMP END PARALLEL DO

Note: ROUP recognizes both Fortran syntax (DO) and C syntax (FOR) for compatibility:

  • !$OMP DO → Fortran-specific (preferred for Fortran code)
  • !$OMP FOR → C/C++ syntax (also works in Fortran mode)
  • !$OMP PARALLEL DO → Fortran-specific
  • !$OMP PARALLEL FOR → C/C++ syntax (also works in Fortran mode)

Language Constants

ROUP provides language format constants for Fortran parsing:

#define ROUP_LANG_C                0  // C/C++ (#pragma omp)
#define ROUP_LANG_FORTRAN_FREE     1  // Fortran free-form (!$OMP)
#define ROUP_LANG_FORTRAN_FIXED    2  // Fortran fixed-form (!$OMP or C$OMP)

Using the Rust API

Parsing Fortran Directives

use roup::lexer::Language;
use roup::parser::openmp;

// Create parser with Fortran free-form support
let parser = openmp::parser().with_language(Language::FortranFree);

// Parse a Fortran OpenMP directive
let input = "!$OMP PARALLEL PRIVATE(A, B) NUM_THREADS(4)";
let (rest, directive) = parser.parse(input).expect("parsing should succeed");

println!("Directive: {}", directive.name);
println!("Clauses: {}", directive.clauses.len());

Supported Language Formats

use roup::lexer::Language;

// C/C++ format (default)
let c_parser = openmp::parser().with_language(Language::C);

// Fortran free-form
let fortran_free = openmp::parser().with_language(Language::FortranFree);

// Fortran fixed-form
let fortran_fixed = openmp::parser().with_language(Language::FortranFixed);

Using the C API from Fortran

Setting Up Fortran-C Interoperability

Create an interface module for ROUP C API:

module roup_interface
    use iso_c_binding
    implicit none
    
    ! Language constants
    integer(c_int), parameter :: ROUP_LANG_FORTRAN_FREE = 1
    integer(c_int), parameter :: ROUP_LANG_FORTRAN_FIXED = 2
    
    interface
        ! Parse with language specification
        function roup_parse_with_language(input, language) &
            bind(C, name="roup_parse_with_language")
            use iso_c_binding
            type(c_ptr), value :: input
            integer(c_int), value :: language
            type(c_ptr) :: roup_parse_with_language
        end function roup_parse_with_language
        
        ! Free directive
        subroutine roup_directive_free(directive) &
            bind(C, name="roup_directive_free")
            use iso_c_binding
            type(c_ptr), value :: directive
        end subroutine roup_directive_free
        
        ! Get directive name
        function roup_directive_name(directive) &
            bind(C, name="roup_directive_name")
            use iso_c_binding
            type(c_ptr), value :: directive
            type(c_ptr) :: roup_directive_name
        end function roup_directive_name
        
        ! Get clause count
        function roup_directive_clause_count(directive) &
            bind(C, name="roup_directive_clause_count")
            use iso_c_binding
            type(c_ptr), value :: directive
            integer(c_size_t) :: roup_directive_clause_count
        end function roup_directive_clause_count
    end interface
end module roup_interface

Parsing Fortran Directives from Fortran

program parse_example
    use iso_c_binding
    use roup_interface
    implicit none
    
    type(c_ptr) :: directive_ptr, name_ptr
    character(len=100) :: input = "!$OMP PARALLEL PRIVATE(X)"
    character(kind=c_char), dimension(:), allocatable :: c_input
    integer :: i, n
    
    ! Convert Fortran string to C string
    n = len_trim(input)
    allocate(c_input(n+1))
    do i = 1, n
        c_input(i) = input(i:i)
    end do
    c_input(n+1) = c_null_char
    
    ! Parse directive
    directive_ptr = roup_parse_with_language(c_loc(c_input), &
                                              ROUP_LANG_FORTRAN_FREE)
    
    if (c_associated(directive_ptr)) then
        ! Get directive information
        name_ptr = roup_directive_name(directive_ptr)
        ! ... process name_ptr ...
        
        ! Clean up
        call roup_directive_free(directive_ptr)
    else
        print *, "Parse error"
    end if
    
    deallocate(c_input)
end program parse_example

Case Insensitivity

Fortran is case-insensitive, and ROUP respects this:

! All of these are equivalent
!$OMP PARALLEL PRIVATE(X)
!$omp parallel private(x)
!$Omp Parallel Private(X)

ROUP normalizes Fortran identifiers to lowercase internally while preserving the original case in the parsed output.

Common Fortran OpenMP Constructs

Parallel Regions

!$OMP PARALLEL PRIVATE(TID) SHARED(N)
    ! Parallel code
!$OMP END PARALLEL

Work-Sharing Constructs

In Fortran, use DO instead of C’s for:

!$OMP DO PRIVATE(I) SCHEDULE(STATIC, 10)
    do i = 1, n
        ! Loop body
    end do
!$OMP END DO

Or combined:

!$OMP PARALLEL DO PRIVATE(I,J) REDUCTION(+:SUM)
    do i = 1, n
        sum = sum + array(i)
    end do
!$OMP END PARALLEL DO

Array Sections

Fortran array sections use different syntax than C:

!$OMP PARALLEL PRIVATE(A(1:N), B(:,1:M))
    ! Work with array sections
!$OMP END PARALLEL

Common Blocks

Fortran’s THREADPRIVATE can apply to common blocks:

      COMMON /MYDATA/ X, Y, Z
!$OMP THREADPRIVATE(/MYDATA/)

Examples

See the examples/fortran/ directory for complete working examples:

  • basic_parse.f90: Simple Fortran directive examples
  • tutorial_basic.f90: Full C API integration tutorial

Building Fortran Programs with ROUP

Using gfortran

# Compile Fortran code
gfortran -c my_program.f90

# Link with ROUP library
gcc my_program.o -L/path/to/roup/target/release -lroup -o my_program

# Run (set LD_LIBRARY_PATH)
LD_LIBRARY_PATH=/path/to/roup/target/release ./my_program

Makefile Example

FC = gfortran
ROUP_LIB = -L../../target/release -lroup -Wl,-rpath,../../target/release

my_program: my_program.f90
	$(FC) -o $@ $< $(ROUP_LIB)

Known Limitations

⚠️ Current limitations in experimental Fortran support:

  1. Continuation Syntax Requirements: Multi-line directives must use standard continuation markers

    • Fortran: Terminate continued lines with & and optionally repeat the sentinel on the next line
    • C/C++: Place a trailing \ at the end of each continued line
    • See Line Continuations for canonical examples across languages
  2. End Directives: !$OMP END PARALLEL and similar end directives may not parse correctly

  3. Array Sections: Complex array section syntax may have issues

  4. Fixed-Form Column Rules: Strict column 1-6 sentinel placement not enforced

  5. Fortran-Specific Directives: Some Fortran-only directives (e.g., WORKSHARE) may not be registered

Troubleshooting

Parse Errors

If parsing fails:

  1. Check sentinel format: Use !$OMP for free-form or !$OMP/C$OMP for fixed-form
  2. Verify case: While case-insensitive, ensure proper formatting
  3. Check whitespace: Ensure proper spacing after sentinel
  4. Use correct language mode: Specify ROUP_LANG_FORTRAN_FREE or ROUP_LANG_FORTRAN_FIXED

Directive Not Found

Some directives may not be in the registry. Check:

  • Is the directive name correct? (Fortran supports both DO and FOR syntax)
  • Is it a composite directive? (Use PARALLEL DO not PARALLEL + DO)

API Reference

See:

Contributing

Fortran support is under active development. Contributions welcome:

  • Test with real Fortran+OpenMP code
  • Report parsing issues
  • Add more Fortran-specific test cases
  • Improve documentation

Further Reading

ompparser Compatibility Layer

ROUP provides a drop-in compatibility layer for projects using ompparser, allowing you to switch to ROUP’s faster, safer Rust-based parser without changing your code.

What is it?

A compatibility layer that provides:

  • Same API as ompparser - no code changes needed
  • Drop-in replacement via libompparser.so
  • ROUP backend - expected-to-be faster, safer parsing in Rust
  • Reuses ompparser methods - toString(), generateDOT(), etc. (zero duplication)

Quick Start

One-Command Build

cd compat/ompparser
./build.sh

The script will:

  1. Check prerequisites (git, cmake, gcc, cargo)
  2. Initialize ompparser submodule
  3. Build ROUP core library
  4. Build libompparser.so (size varies by build configuration)
  5. Run the upstream ompparser ctest suite

Manual Build

# 1. Initialize ompparser submodule
git submodule update --init --recursive

# 2. Build ROUP core
cd /path/to/roup
cargo build --release

# 3. Build compatibility layer
cd compat/ompparser
mkdir -p build && cd build
cmake ..
make

# 4. Run tests
ctest --output-on-failure

Usage

Drop-in Replacement

Install system-wide and use exactly like original ompparser:

# Install
cd compat/ompparser/build
sudo make install
sudo ldconfig

# Use (unchanged!)
g++ mycompiler.cpp -lompparser -o mycompiler

Code Example

Your existing ompparser code works without changes:

#include <OpenMPIR.h>
#include <iostream>

int main() {
    // Parse OpenMP directive
    OpenMPDirective* dir = parseOpenMP("omp parallel num_threads(4)", nullptr);
    
    if (dir) {
        // Use ompparser methods (all work!)
        std::cout << "Kind: " << dir->getKind() << std::endl;
        std::cout << "String: " << dir->toString() << std::endl;
        
        // Access clauses
        auto* clauses = dir->getAllClauses();
        std::cout << "Clauses: " << clauses->size() << std::endl;
        
        delete dir;
    }
    
    return 0;
}

CMake Integration

Option 1: pkg-config

find_package(PkgConfig REQUIRED)
pkg_check_modules(OMPPARSER REQUIRED ompparser)

target_link_libraries(your_app ${OMPPARSER_LIBRARIES})
target_include_directories(your_app PRIVATE ${OMPPARSER_INCLUDE_DIRS})

Option 2: Direct linking

target_link_libraries(your_app
    ${PATH_TO_ROUP}/compat/ompparser/build/libompparser.so
)

What’s Included

libompparser.so

Single self-contained library with:

  • ROUP parser (statically embedded) - Rust-based, safe parsing
  • ompparser methods - toString, generateDOT, etc.
  • Compatibility wrapper - Seamless integration layer
  • Self-contained - No libroup.so dependency (system libs via libc)

Comprehensive Testing

The shim is validated via the ompparser ctest suite bundled in the submodule. It covers directive kinds (including combined/end-paired forms), clause parameters, unparsing, and language modes.

Architecture

Your Application (OpenMP directives to parse)
    ↓
compat_impl.cpp (~190 lines) - Minimal wrapper
    ↓
ROUP C API (roup_parse, roup_directive_kind, etc.)
    ↓
ROUP Rust Parser (safe parser core)
    ↓
Returns: OpenMPDirective with ompparser methods

Key Design:

  • Reuses 90% of ompparser code (no duplication)
  • Git submodule approach - automatic ompparser upgrades
  • Minimal unsafe code (~60 lines, 0.9%), all at FFI boundary

Known Limitations

The shim uses the generated ROUP_OMPD_*/ROUP_OMPC_* constants from src/roup_constants.h and preserves the canonical keyword spellings from ROUP. Legacy ompparser enum values are mapped internally.

Documentation

Complete documentation in compat/ompparser/:

  • README.md - Complete compatibility layer guide with build instructions and examples

For detailed ROUP API documentation, see API Reference.

Requirements

  • Rust toolchain (for ROUP core)
  • CMake 3.10+
  • C++11 compiler (gcc/clang)
  • Git (for submodule management)

CI/CD

The compatibility layer is tested automatically via GitHub Actions (.github/workflows/build.yml):

- Tests ROUP core (always)
- Tests compat layer (if submodule initialized)
- Verifies library builds successfully
- Validates drop-in functionality
- Checks constants synchronization (checksum validation)

FAQ

Q: Do I need to change my code?
A: No! It’s a drop-in replacement with the same API.

Q: What if I don’t need compat layer?
A: ROUP works perfectly standalone. The compat layer is optional.

Q: How do I get ompparser upgrades?
A: git submodule update --remote pulls latest ompparser automatically.

Q: What about performance?
A: ROUP is expected to be faster than original ompparser due to Rust optimizations.

Q: Is it stable?
A: ⚠️ Experimental stage - thoroughly tested (46 tests) but under active development.

Support

accparser Compatibility Layer

ROUP provides a drop-in compatibility layer for projects using accparser, allowing you to switch to ROUP’s faster, safer Rust-based parser without changing your code.

What is it?

A compatibility layer that provides:

  • Same API as accparser - no code changes needed
  • Drop-in replacement via libaccparser.so
  • Zero ANTLR4 dependency - no need to install antlr4 runtime or toolchain
  • ROUP backend - faster, safer parsing in Rust
  • Reuses accparser methods - toString(), generateDOT(), etc. (zero duplication)

Quick Start

One-Command Build

cd compat/accparser
./build.sh

The script will:

  1. Check prerequisites (git, cmake, gcc, cargo)
  2. Initialize accparser submodule
  3. Build ROUP core library
  4. Build libaccparser.so
  5. Run the full upstream accparser ctest suite (900+ cases)

Manual Build

# 1. Initialize accparser submodule
git submodule update --init --recursive

# 2. Build ROUP core
cd /path/to/roup
cargo build --release

# 3. Build compatibility layer
cd compat/accparser
mkdir -p build && cd build
cmake ..
make

# 4. Run tests
ctest --output-on-failure

Usage

Drop-in Replacement

Install system-wide and use exactly like original accparser:

# Install
cd compat/accparser/build
sudo make install
sudo ldconfig

# Use (unchanged!)
g++ mycompiler.cpp -laccparser -o mycompiler

Code Example

Your existing accparser code works without changes:

#include <OpenACCIR.h>
#include <iostream>

int main() {
    // Set language mode
    setLang(ACC_Lang_C);

    // Parse OpenACC directive
    OpenACCDirective* dir = parseOpenACC("acc parallel num_gangs(4)", nullptr);

    if (dir) {
        // Use accparser methods (all work!)
        std::cout << "Kind: " << dir->getKind() << std::endl;
        std::cout << "String: " << dir->toString() << std::endl;

        // Access clauses
        auto* clauses = dir->getAllClauses();
        std::cout << "Clauses: " << clauses->size() << std::endl;

        delete dir;
    }

    return 0;
}

CMake Integration

Option 1: pkg-config

find_package(PkgConfig REQUIRED)
pkg_check_modules(ACCPARSER REQUIRED accparser)

target_link_libraries(your_app ${ACCPARSER_LIBRARIES})
target_include_directories(your_app PRIVATE ${ACCPARSER_INCLUDE_DIRS})

Option 2: Direct linking

target_link_libraries(your_app
    ${PATH_TO_ROUP}/compat/accparser/build/libaccparser.so
)

What’s Included

libaccparser.so

Single self-contained library with:

  • ROUP parser (statically embedded) - Rust-based, safe parsing
  • accparser methods - toString, etc.
  • Compatibility wrapper - Seamless integration layer
  • Self-contained - No libroup.so dependency

Comprehensive Testing

The bundled ctest run exercises the upstream accparser test matrix (directives, clauses, aliases, unparsing) against the ROUP-backed shim. It also validates end-paired directives, wait/cache data, and alias spellings under the ROUP_ACC_* namespace.

Architecture

Your Application (OpenACC directives to parse)
    ↓
compat_impl.cpp (~220 lines) - Minimal wrapper
    ↓
ROUP C API (acc_parse, acc_directive_kind, etc.)
    ↓
ROUP Rust Parser (safe parser core)
    ↓
Returns: OpenACCDirective with accparser methods

Key Design:

  • Reuses 90% of accparser code (no duplication)
  • Git submodule approach - automatic accparser upgrades
  • No ANTLR4 dependency - cleaner build process
  • Minimal unsafe code, all at FFI boundary

Supported Features

Directives (17+)

  • Compute: parallel, kernels, serial, loop
  • Data: data, enter data, exit data, host_data
  • Synchronization: wait, atomic
  • Other: declare, routine, init, shutdown, set, update, end

Clauses (45+)

  • Compute: num_gangs, num_workers, vector_length, async, wait
  • Data: copy, copyin, copyout, create, delete, present
  • Loop: gang, worker, vector, seq, independent, collapse, tile
  • Other: private, firstprivate, reduction, if, default

See compat/accparser/src/roup_constants.h (and the auto-generated src/roup_constants.h) for the complete list of generated macros. Note that generated OpenACC macros now use the ROUP_ACC_* prefix (for example ROUP_ACCD_parallel and ROUP_ACCC_async). The legacy ACC_* aliases are not emitted by the generator.

Known Limitations

The shim inherits ROUP’s canonical token choices. Generated OpenACC macros use the ROUP_ACC_* prefix (for example ROUP_ACCD_parallel); legacy ACC_* names are intentionally not emitted.

Documentation

Complete documentation in compat/accparser/:

  • README.md - Complete compatibility layer guide with build instructions and examples

For detailed ROUP API documentation, see API Reference.

Requirements

  • Rust toolchain (for ROUP core)
  • CMake 3.10+
  • C++11 compiler (gcc/clang)
  • Git (for submodule management)
  • NO antlr4 required!

CI/CD

The compatibility layer is tested automatically via GitHub Actions (.github/workflows/ci.yml):

- Tests ROUP core (always)
- Tests compat layer (if submodule initialized)
- Verifies library builds successfully
- Validates drop-in functionality
- Checks constants synchronization (checksum validation)

FAQ

Q: Do I need to change my code? A: No! It’s a drop-in replacement with the same API.

Q: What about ANTLR4 dependency? A: ROUP completely eliminates the ANTLR4 dependency. You only need Rust, CMake, and a C++ compiler.

Q: What if I don’t need compat layer? A: ROUP works perfectly standalone. The compat layer is optional.

Q: How do I get accparser upgrades? A: git submodule update --remote pulls latest accparser automatically.

Q: What about performance? A: ROUP’s hand-written parser is 2-5x faster than ANTLR-generated parsers for typical OpenACC pragmas.

Q: Is it stable? A: Thoroughly tested (38 tests) and ready for use. All tests passing.

Support

API Reference

This page points to the primary surfaces for Rust and C/C++/Fortran users. Full Rustdoc is generated as part of the build.


Rust

  • Rustdoc: cargo doc --no-deps generates the full API docs.
  • Parsing: parser::openmp::parser() and parser::openacc::parser() return a Parser that accepts C or Fortran sentinels via with_language(...). Convenience helpers parse_omp_directive / parse_acc_directive are also available.
  • AST/IR: Parser::parse_ast(...) converts parsed directives into the IR layer for semantic queries and unparsing.
  • Translation: ir::translate::{translate_c_to_fortran, translate_fortran_to_c} (and their _ir variants) map OpenMP directives between languages.
  • Debugger: roup_debug binary wraps debugger::DebugSession for interactive and batch stepping.

Quick example:

use roup::parser::openmp;
use roup::lexer::Language;

let parser = openmp::parser().with_language(Language::C);
let (_, directive) = parser.parse("#pragma omp parallel for").unwrap();
println!("name: {}", directive.name);

C / OpenACC APIs

src/roup_constants.h is generated at build time and contains:

  • Directive/clause macro constants (ROUP_OMPD_*, ROUP_OMPC_*, ROUP_ACCD_*, ROUP_ACCC_*)
  • Language codes: ROUP_LANG_C = 0, ROUP_LANG_FORTRAN_FREE = 1, ROUP_LANG_FORTRAN_FIXED = 2
  • Unknown sentinel: -1

Key function families (OpenMP):

  • roup_parse, roup_parse_with_language
  • Iterators: roup_directive_clauses_iter, roup_clause_iterator_next, *_free
  • Queries: roup_directive_kind, roup_directive_clause_count, roup_clause_kind, schedule/reduction/default queries, variable lists
  • Translation helpers: roup_convert_language, roup_string_free

Key function families (OpenACC):

  • acc_parse, acc_parse_with_language, acc_directive_free
  • Clause queries: acc_clause_kind, acc_clause_expressions_count, acc_clause_expression_at, acc_clause_modifier, acc_clause_operator, acc_clause_original_keyword
  • Directive extras: cache and wait accessors, routine name, end-paired mapping

Error handling:

  • Functions returning pointers (e.g., *_parse, *_iter, string accessors) return NULL on error and must be checked before use.
  • Functions returning integers (e.g., kind lookups) return -1 for invalid inputs or unknown kinds.

C++ RAII

See the C++ tutorial for RAII wrappers that call the C API and auto-free resources.

OpenMP support

ROUP registers every directive and clause keyword from the OpenMP 6.0 specification. The canonical listings live in the generated reference chapters:

Quick facts

FeatureDetails
OpenMP versions3.0 – 6.0
Directive/Clause coverageComplete registry mirroring the spec
LanguagesC, C++, Fortran
Automated testsKeyword coverage, round-trips, IR/display parity, and VV suites

Unknown directives and clauses are rejected so unsupported constructs fail fast. The tests/openmp_keyword_coverage.rs integration test keeps the registry in sync with the specification, and additional round-trip tests validate parsing, IR conversion, and display.

OpenACC support

ROUP implements the complete OpenACC 3.4 directive and clause vocabulary. The reference chapters below catalogue every keyword and restriction with section and page references back to the official specification PDF.

Quick facts

FeatureDetails
OpenACC version3.4
Directive keywords24 (including space/underscore aliases)
Clause keywords49
LanguagesC, C++, Fortran (free & fixed form)
Automated testsFull directive and clause round-trip coverage

Unknown directives and clauses are rejected so unsupported constructs fail fast. The tests/openacc_roundtrip.rs suite keeps the registry in sync with the specification and exercises round-trip formatting for every keyword, and the C API mappings are validated to guarantee consistent clause kind values.

OpenACC 3.4 Directives and Clauses

This comprehensive reference catalogue documents all OpenACC 3.4 keywords from the OpenACC Application Programming Interface Version 3.4 specification.

Purpose

This document serves as a complete keyword inventory for development and reference. Each entry includes:

  • Specification section and page numbers
  • Categorization and properties
  • No duplication - each keyword appears once

Coverage

  • 24 Directives/Constructs - All compute, data, loop, synchronization, declaration, and runtime directives (including space/underscore alias forms)
  • 50+ Clauses - All clause keywords
  • Modifiers - Data clause modifiers, gang/worker/vector modifiers, collapse modifiers
  • Special Values - Async values, device types, default values
  • Reduction Operators - All supported reduction operations
  • Parallelism Levels - Gang, worker, vector, seq

Directives and Constructs

Compute Constructs

  • parallel (§2.5.1; p.33; category: compute; association: block; properties: creates gang-worker-vector parallelism)
  • serial (§2.5.2; p.34; category: compute; association: block; properties: serialized execution on device)
  • kernels (§2.5.3; p.35; category: compute; association: block; properties: compiler-optimized kernel launch)

Data Constructs

  • data (§2.6.5; p.43; category: data; association: block; properties: structured data lifetime)
  • enter data (§2.6.6; p.45; category: data; association: executable; properties: dynamic data region entry)
  • exit data (§2.6.6; p.45; category: data; association: executable; properties: dynamic data region exit)
  • host_data (§2.8; p.62; category: data; association: block; properties: host pointer mapping)

Loop Constructs

  • loop (§2.9; p.64; category: loop; association: loop nest; properties: loop parallelization)
  • parallel loop (§2.11; p.75; category: combined; association: loop nest; properties: parallel + loop combined)
  • serial loop (§2.11; p.75; category: combined; association: loop nest; properties: serial + loop combined)
  • kernels loop (§2.11; p.75; category: combined; association: loop nest; properties: kernels + loop combined)

Synchronization Constructs

  • atomic (§2.12; p.77; category: synchronization; association: statement; properties: atomic memory operations)
  • cache (§2.10; p.75; category: synchronization; association: loop; properties: cache hint)
  • wait (§2.16.3; p.100; category: synchronization; association: executable; properties: async queue synchronization)

Declaration Directives

  • declare (§2.13; p.81; category: declarative; association: scope; properties: device data declaration)
  • routine (§2.15.1; p.91; category: declarative; association: function; properties: device routine declaration)

Runtime Directives

  • init (§2.14.1; p.84; category: runtime; association: executable; properties: device initialization)
  • shutdown (§2.14.2; p.85; category: runtime; association: executable; properties: device shutdown)
  • set (§2.14.3; p.87; category: runtime; association: executable; properties: runtime configuration)
  • update (§2.14.4; p.88; category: runtime; association: executable; properties: explicit data transfer)

Special Constructs

  • do concurrent (§2.17.2; p.102; category: integration; association: Fortran; properties: Fortran do concurrent mapping)

Clauses

Compute Clauses

  • if (§2.5.6; p.37; category: conditional; applicable to: parallel, serial, kernels, host_data, atomic, init, set, update)
  • self (§2.5.7; p.37; category: conditional; applicable to: parallel, serial, kernels; properties: execute on host without data movement)
  • async (§2.5.8, §2.16.1; pp.37, 99; category: synchronization; applicable to: parallel, serial, kernels, data, enter data, exit data, update, wait)
  • wait (§2.5.9, §2.16.2; pp.37, 100; category: synchronization; applicable to: parallel, serial, kernels, data, enter data, exit data, update)
  • num_gangs (§2.5.10; p.37; category: parallelism; applicable to: parallel, kernels; properties: specifies number of gangs)
  • num_workers (§2.5.11; p.38; category: parallelism; applicable to: parallel, kernels; properties: specifies workers per gang)
  • vector_length (§2.5.12; p.38; category: parallelism; applicable to: parallel, kernels; properties: specifies vector length per worker)
  • private (§2.5.13, §2.9.10; pp.38, 70; category: data sharing; applicable to: parallel, serial, kernels, loop)
  • firstprivate (§2.5.14; p.38; category: data sharing; applicable to: parallel, serial, kernels; properties: initialized private variables)
  • reduction (§2.5.15, §2.9.11; pp.39, 71; category: data sharing; applicable to: parallel, kernels, loop; properties: reduction operations)
  • default (§2.5.16; p.40; category: data sharing; applicable to: parallel, serial, kernels, data; properties: values are none or present)

Data Clauses

  • copy (§2.7.7; p.54; category: data movement; properties: copy in and copy out)
  • copyin (§2.7.8; p.55; category: data movement; properties: copy to device)
  • copyout (§2.7.9; p.56; category: data movement; properties: copy from device)
  • create (§2.7.10, §2.13.2; pp.57, 83; category: data allocation; properties: allocate on device)
  • no_create (§2.7.11; p.57; category: data allocation; properties: use if present, don’t create)
  • delete (§2.7.12; p.58; category: data allocation; properties: deallocate from device)
  • present (§2.7.6; p.53; category: data presence; properties: data must be present on device)
  • deviceptr (§2.7.5; p.53; category: data presence; properties: device pointer)
  • attach (§2.7.13; p.59; category: pointer; properties: attach pointer to device address)
  • detach (§2.7.14; p.59; category: pointer; properties: detach pointer from device address)

Synonyms: The specification preserves historical aliases such as pcopy, pcopyin, pcopyout, pcreate and their present_or_* counterparts. ROUP registers each alias alongside the canonical clause name so original source spellings are retained during parsing.

Host-Device Interaction Clauses

  • use_device (§2.8.1; p.63; category: host access; applicable to: host_data; properties: map device pointers to host)
  • if_present (§2.8.3; p.63; category: conditional; applicable to: host_data, update; properties: conditional on presence)

Loop Clauses

  • collapse (§2.9.1; p.65; category: loop transformation; applicable to: loop; properties: collapse nested loops)
  • gang (§2.9.2; p.66; category: parallelism; applicable to: loop; properties: gang-level parallelism)
  • worker (§2.9.3; p.68; category: parallelism; applicable to: loop; properties: worker-level parallelism)
  • vector (§2.9.4; p.68; category: parallelism; applicable to: loop; properties: vector-level parallelism)
  • seq (§2.9.5; p.68; category: parallelism; applicable to: loop; properties: sequential execution)
  • independent (§2.9.6; p.69; category: parallelism; applicable to: loop; properties: loop iterations are independent)
  • auto (§2.9.7; p.69; category: parallelism; applicable to: loop; properties: compiler decides parallelism)
  • tile (§2.9.8; p.69; category: loop transformation; applicable to: loop; properties: tile nested loops)
  • device_type (§2.9.9; p.70; category: device-specific; applicable to: loop, compute constructs; properties: device-specific clauses)

Declaration Clauses

  • device_resident (§2.13.1; p.82; category: data declaration; applicable to: declare; properties: data resides on device)
  • link (§2.13.3; p.84; category: data declaration; applicable to: declare; properties: static device linkage)

Special Clauses

  • finalize (§2.6.6; p.46; category: data management; applicable to: exit data; properties: force deallocation)
  • bind (§2.15.1; p.92; category: routine; applicable to: routine; properties: specify device routine name)
  • nohost (§2.15.1; p.93; category: routine; applicable to: routine; properties: routine only on device)

Modifiers

Modifiers are keywords that modify the behavior of clauses. They appear as part of clause syntax to refine clause semantics.

Data Clause Modifiers

  • always (§2.7.4; p.52; data clause modifier; forces data transfer even if present)
  • zero (§2.7.4; p.52; data clause modifier; zero memory on allocation)
  • readonly (§2.7.4; p.52; data clause modifier; read-only access)

Gang Clause Modifiers

  • num (§2.9.2; p.66; gang modifier; specifies number of gangs)
  • dim (§2.9.2; p.67; gang modifier; specifies gang dimension)
  • static (§2.9.2; p.67; gang modifier; static gang distribution)

Worker Clause Modifiers

  • num (§2.9.3; p.68; worker modifier; specifies number of workers)

Vector Clause Modifiers

  • length (§2.9.4; p.68; vector modifier; specifies vector length)

Collapse Clause Modifiers

  • force (§2.9.1; p.65; collapse modifier; force collapse even with dependencies)

Cache Clause Modifiers

  • readonly (§2.10; p.75; cache modifier; read-only cache hint)

Special Values and Constants

Async Values

  • acc_async_default (§2.16; p.98; async value; default async queue)
  • acc_async_noval (§2.16; p.98; async value; no async queue specified)
  • acc_async_sync (§2.16; p.98; async value; synchronous execution)

Device Types

  • * (§2.4; p.31; device type; all device types)
  • host (§2.4; p.31; device type; host device)
  • nvidia (§2.4; p.31; device type; NVIDIA devices)
  • radeon (§2.4; p.31; device type; AMD Radeon devices)
  • default (§2.4; p.31; device type; implementation default)

Default Clause Values

  • none (§2.5.16; p.40; default value; no implicit data sharing)
  • present (§2.5.16; p.40; default value; assume all data present)

Reduction Operators

Operators used with the reduction clause for parallel reduction operations.

Arithmetic Operators

  • + (§2.5.15, §2.9.11; pp.39, 71; addition)
  • * (§2.5.15, §2.9.11; pp.39, 71; multiplication)
  • max (§2.5.15, §2.9.11; pp.39, 71; maximum value)
  • min (§2.5.15, §2.9.11; pp.39, 71; minimum value)

Bitwise Operators

  • & (§2.5.15, §2.9.11; pp.39, 71; bitwise AND)
  • | (§2.5.15, §2.9.11; pp.39, 71; bitwise OR)
  • ^ (§2.5.15, §2.9.11; pp.39, 71; bitwise XOR)

Logical Operators

  • && (§2.5.15, §2.9.11; pp.39, 71; logical AND)
  • || (§2.5.15, §2.9.11; pp.39, 71; logical OR)

Fortran-Specific Operators

  • .and. (§2.5.15, §2.9.11; pp.39, 71; Fortran logical AND)
  • .or. (§2.5.15, §2.9.11; pp.39, 71; Fortran logical OR)
  • .eqv. (§2.5.15, §2.9.11; pp.39, 71; Fortran logical equivalence)
  • .neqv. (§2.5.15, §2.9.11; pp.39, 71; Fortran logical non-equivalence)
  • iand (§2.5.15, §2.9.11; pp.39, 71; Fortran bitwise AND)
  • ior (§2.5.15, §2.9.11; pp.39, 71; Fortran bitwise OR)
  • ieor (§2.5.15, §2.9.11; pp.39, 71; Fortran bitwise XOR)

Parallelism Levels

OpenACC defines a three-level parallelism hierarchy:

  • gang (§2.2.3; p.23; parallelism level; coarse-grain parallelism, analogous to thread blocks)
  • worker (§2.2.3; p.23; parallelism level; medium-grain parallelism, analogous to threads)
  • vector (§2.2.3; p.23; parallelism level; fine-grain parallelism, analogous to SIMD lanes)
  • seq (§2.9.5; p.68; parallelism level; sequential execution, no parallelism)

Atomic Operation Keywords

  • read (§2.12; p.77; atomic operation; atomic read)
  • write (§2.12; p.78; atomic operation; atomic write)
  • update (§2.12; p.78; atomic operation; atomic update)
  • capture (§2.12; p.79; atomic operation; atomic capture)

Runtime Clause Keywords

Set Directive Clauses

  • device_type (§2.14.3; p.87; applicable to: set; specifies device type)
  • device_num (§2.14.3; p.87; applicable to: set; specifies device number)
  • default_async (§2.14.3; p.87; applicable to: set; sets default async queue)

Update Directive Clauses

  • self (§2.14.4; p.88; applicable to: update; copy to host)
  • host (§2.14.4; p.88; applicable to: update; alias for self)
  • device (§2.14.4; p.88; applicable to: update; copy to device)

Routine Directive Clauses

  • gang (§2.15.1; p.92; applicable to: routine; routine contains gang-level parallelism)
  • worker (§2.15.1; p.92; applicable to: routine; routine contains worker-level parallelism)
  • vector (§2.15.1; p.92; applicable to: routine; routine contains vector-level parallelism)
  • seq (§2.15.1; p.92; applicable to: routine; routine is sequential)

OpenACC 3.4 Directive–Clause Matrix

This matrix cross-references every OpenACC 3.4 directive with its allowed clauses and enumerates the clause-level modifiers and arguments. Section numbers and page references point back to the canonical OpenACC 3.4 PDF so that every entry can be validated directly against the specification. Use this document together with the directive/clauses index and the restrictions digest to obtain a complete, single-source view of the standard.

Directive coverage

Parallel construct (§2.5.1, p.33)

  • async [(async-argument)] — asynchronous queue selection; semantics defined in §2.16.1 (p.99) with async-argument rules in §2.16 (p.98).
  • wait [(wait-argument)] — queue synchronization; wait-argument syntax in §2.16 (p.99) and clause behavior in §2.16.2 (p.100).
  • num_gangs(int-expr-list) — up to three gang dimensions (missing entries default to 1) for parallel regions; details in §2.5.10 (p.37).
  • num_workers(int-expr) — worker count per gang (§2.5.11, p.38).
  • vector_length(int-expr) — vector lane count per worker (§2.5.12, p.38).
  • device_type(device-type-list) — device-specific clause selection (§2.4, p.31).
  • if(condition) — host vs device execution control (§2.5.6, p.37).
  • self[(condition)] — execute region on host without moving data (§2.5.7, p.37).
  • reduction(operator:var-list) — reduction variables imply copy semantics (§2.5.15, p.39).
  • Data clauses copy, copyin, copyout, create, no_create, present, deviceptr, attach each accept optional modifier lists from §2.7.4 (p.52) and actions defined in §§2.7.1–2.7.14 (pp.48–60).
  • private(var-list) — private instances per gang (§2.5.13, p.38).
  • firstprivate(var-list) — initialize privates from host values (§2.5.14, p.38).
  • default(none|present) — default data scoping (§2.5.16, p.40).

Serial construct (§2.5.2, p.34)

  • Permits the same clauses as the parallel construct except that num_gangs, num_workers, and vector_length are forbidden (§2.5.2, p.34). Other clause semantics match the sections cited above.

Kernels construct (§2.5.3, p.35)

  • async[(async-argument)] and wait[(wait-argument)] per §§2.16.1–2.16.2 (pp.99–100).
  • num_gangs(int-expr) — single argument specifying gangs per kernel (§2.5.10, p.37).
  • num_workers(int-expr) and vector_length(int-expr) as in §§2.5.11–2.5.12 (p.38).
  • device_type, if, self, and all data clauses (copy, copyin, copyout, create, no_create, present, deviceptr, attach) with modifiers per §§2.4 and 2.7.
  • default(none|present) per §2.5.16 (p.40).

Data construct (§2.6.5, p.43)

  • if(condition) for conditional region creation (§2.6.5, p.43).
  • async[(async-argument)] and wait[(wait-argument)] per §§2.16.1–2.16.2 (pp.99–100).
  • device_type(device-type-list) per §2.4 (p.31).
  • Data movement clauses copy, copyin, copyout, create, no_create, present, deviceptr, attach with modifier lists from §2.7.4 (p.52) and semantics in §§2.7.1–2.7.14 (pp.48–60).
  • default(none|present) (treated as in §2.5.16, p.40).

Enter data directive (§2.6.6, p.45)

  • if(condition) optional guard (§2.6.6, p.45).
  • async[(async-argument)] and wait[(wait-argument)] per §§2.16.1–2.16.2 (pp.99–100).
  • copyin([modifier-list:]var-list), create([modifier-list:]var-list), and attach(var-list) with data clause modifiers from §2.7.4 (p.52).

Exit data directive (§2.6.6, p.45)

  • if(condition), async[(async-argument)], wait[(wait-argument)] as above.
  • copyout([modifier-list:]var-list), delete(var-list), detach(var-list) with modifiers from §2.7.4 (p.52) and clause semantics in §§2.7.9–2.7.14 (pp.56–60).
  • finalize — forces dynamic reference counters to zero (§2.6.6, p.46).

Host_data construct (§2.8, p.62)

  • use_device(var-list) — maps host pointers to device addresses (§2.8.1, p.63).
  • if(condition) and if_present clauses (§§2.8.2–2.8.3, p.63).

Loop construct (§2.9, p.64)

  • collapse([force:]n) — loop nest collapsing with optional force qualifier (§2.9.1, p.65).
  • gang[(gang-arg-list)] — optional num:, dim:, and static: modifiers per §2.9.2 (pp.66–67).
  • worker[( [num:]int-expr )] (§2.9.3, p.68).
  • vector[( [length:]int-expr )] (§2.9.4, p.68).
  • seq, independent, and auto exclusivity rules in §§2.9.5–2.9.7 (pp.68–69).
  • tile(size-expr-list) with optional * entries (§2.9.8, p.69).
  • device_type(device-type-list) per §2.9.9 (p.70).
  • private(var-list) (§2.9.10, p.70) and reduction(operator:var-list) (§2.9.11, p.71).

Cache directive (§2.10, p.75)

  • cache([readonly:]var-list) — optional readonly modifier constrains writes (§2.10, p.75).

Combined constructs (§2.11, p.75)

  • parallel loop, serial loop, and kernels loop accept any clause allowed on both the outer construct and the loop construct; reductions imply copy semantics (§2.11, pp.75–76).

Atomic construct (§2.12, pp.77–80)

  • Optional atomic-clause of read, write, update, or capture; Fortran syntax variants follow §2.12 (pp.77–80).
  • Optional if(condition) clause (§2.12, p.77).

Declare directive (§2.13, pp.81–84)

  • Data clauses copy, copyin, copyout, create, present, deviceptr as in §2.13 (pp.82–83).
  • device_resident(var-list) (§2.13.1, p.82).
  • link(var-list) for static linkage of device allocations (§2.13.3, p.84).

Init directive (§2.14.1, p.84)

  • device_type(device-type-list) and device_num(int-expr) to select targets (§2.14.1, p.84).
  • Optional if(condition) guard (§2.14.1, p.84).

Shutdown directive (§2.14.2, p.85)

  • Same clause set as init: device_type, device_num, and optional if(condition) (§2.14.2, p.85).

Set directive (§2.14.3, p.87)

  • default_async(async-argument) — sets the default queue (§2.14.3, p.87).
  • device_num(int-expr) and device_type(device-type-list) adjust internal control variables (§2.14.3, p.87).
  • Optional if(condition) (§2.14.3, p.87).

Update directive (§2.14.4, p.88)

  • async[(async-argument)], wait[(wait-argument)], device_type(device-type-list), and if(condition) as above.
  • if_present skip modifier (§2.14.4, p.89).
  • Data movement clauses self(var-list), host(var-list), device(var-list) with semantics in §2.14.4 (pp.88–89).

Wait directive (§2.16.3, p.100; see also §2.14.5)

  • Optional wait-argument tuple [devnum:int-expr:][queues:]async-argument-list per §2.16 (p.99).
  • Optional async[(async-argument)] to queue the wait (§2.16.3, p.100).
  • Optional if(condition) (§2.16.3, p.100).

Routine directive (§2.15.1, pp.91–97)

  • Parallelism clauses gang[(dim:int-expr)], worker, vector, and seq define callable levels (§2.15.1, pp.91–93).
  • bind(name|string) for device linkage (§2.15.1, pp.93–94).
  • device_type(device-type-list) for specialization (§2.15.1, pp.94–95).
  • nohost to omit host compilation (§2.15.1, pp.94–95).

Do concurrent integration (§2.17.2, p.102)

  • When combined with loop constructs, local, local_init, shared, and default(none) locality specs map to private, firstprivate, copy, and default(none) clauses on the enclosing compute construct (§2.17.2, p.102).

Clause reference

Device-specific clause (§2.4, pp.31–33)

  • device_type(device-type-list) partitions clause lists by architecture name or *; default clauses apply when no device-specific override exists (§2.4, pp.31–33).
  • Abbreviation dtype is permitted (§2.4, p.31).
  • Device-specific clauses are limited per directive as documented in each directive section.

if clause (§§2.5.6 & 2.8.2, p.37 & p.63)

  • Compute constructs: true runs on the device; false reverts to host execution (§2.5.6, p.37).
  • Host_data: governs creation of device pointer aliases (§2.8.2, p.63).
  • Enter/exit/update data: conditional data movement (§2.6.6, p.45; §2.14.4, p.88).

self clause (§§2.5.7 & 2.14.4, pp.37 & 88)

  • On compute constructs, self[(condition)] forces host execution when true (§2.5.7, p.37).
  • On update, self(var-list) copies from device to host for uncaptured data (§2.14.4, p.88).

async clause (§2.16.1, p.99)

  • Allowed on parallel, serial, kernels, data constructs, enter/exit data, update, and wait directives (§2.16.1, p.99).
  • async-argument values: nonnegative integers or acc_async_default, acc_async_noval, acc_async_sync (§2.16, p.98).
  • Missing clause implies synchronous execution; empty argument implies acc_async_noval (§2.16.1, p.99).

wait clause (§2.16.2, p.100)

  • Accepts the wait-argument tuple defined in §2.16 (p.99).
  • Without arguments waits on all queues of the current device; with arguments delays launch until specified queues drain (§2.16.2, p.100).

num_gangs clause (§2.5.10, p.37)

  • Parallel construct: up to three integers for gang dimensions; defaults to 1 when omitted (§2.5.10, p.37).
  • Kernels construct: single argument per generated kernel (§2.5.10, p.37).
  • Implementations may cap values based on device limits (§2.5.10, p.37).

num_workers clause (§2.5.11, p.38)

  • Sets workers per gang; unspecified defaults are implementation-defined (§2.5.11, p.38).

vector_length clause (§2.5.12, p.38)

  • Sets vector lanes per worker; unspecified defaults are implementation-defined (§2.5.12, p.38).

private clause (§§2.5.13 & 2.9.10, pp.38 & 70)

  • Compute constructs: allocate private copies for gang members (§2.5.13, p.38).
  • Loop constructs: each iteration gets a private copy; allowed only where clause lists permit (§2.9.10, p.70).

firstprivate clause (§2.5.14, p.38)

  • Initializes private variables from original values at region entry (§2.5.14, p.38).

reduction clause (§§2.5.15 & 2.9.11, pp.39 & 71)

  • Supports operators +, *, max, min, bitwise ops, logical ops, and Fortran iand/ior/ieor with initialization table specified in §2.5.15 (pp.39–40).
  • Applies element-wise to arrays/subarrays; implies appropriate data clauses (§2.5.15, p.39).
  • Loop reductions follow §2.9.11 (pp.71–72).

default clause (§2.5.16, p.40)

  • default(none) requires explicit data clauses; default(present) asserts device presence (§2.5.16, p.40).

Data clause framework (§§2.7–2.7.4, pp.48–53)

  • Data specification syntax in §2.7.1 (pp.48–49).
  • Data actions (copy, create, delete, etc.) in §2.7.2 (pp.50–52).
  • Error conditions in §2.7.3 (p.52).
  • Modifier list tokens: always, alwaysin, alwaysout, capture, readonly, zero (§2.7.4, p.52).

deviceptr clause (§2.7.5, p.53)

  • Treats variables as preallocated device pointers; disallows conflicting data actions (§2.7.5, p.53).

present clause (§2.7.6, p.53)

  • Requires data to exist on the device; raises errors otherwise (§2.7.6, p.53).

copy/copyin/copyout clauses (§§2.7.7–2.7.9, pp.54–56)

  • copy performs in/out transfers; copyin is host→device; copyout is device→host (§§2.7.7–2.7.9, pp.54–56).
  • Respect modifier semantics from §2.7.4.

create clause (§§2.7.10 & 2.13.2, pp.57 & 83)

  • Allocates device storage without transfer (§2.7.10, p.57); declare directive variant described in §2.13.2 (p.83).

no_create clause (§2.7.11, p.57)

  • Asserts that data already exists on device; no allocation occurs (§2.7.11, p.57).

delete clause (§2.7.12, p.58)

  • Deallocates device storage at region exit (§2.7.12, p.58).

attach/detach clauses (§§2.7.13–2.7.14, pp.59–60)

  • Manage pointer attachments to device memory (§§2.7.13–2.7.14, pp.59–60).

use_device clause (§2.8.1, p.63)

  • Temporarily remaps host pointers to device addresses within host_data regions (§2.8.1, p.63).

if_present clause (§§2.8.3 & 2.14.4, pp.63 & 89)

  • Skips operations when data is absent on the device (§2.8.3, p.63; §2.14.4, p.89).

collapse clause (§2.9.1, p.65)

  • Optional force keyword overrides dependency analysis; requires positive iteration counts (§2.9.1, p.65).

gang clause (§2.9.2, pp.66–67)

  • gang-arg-list allows one each of num:, dim:, static: modifiers; dim is limited to 1–3 (§2.9.2, pp.66–67).

worker clause (§2.9.3, p.68)

  • Optional num: argument; interacts with compute scopes as described in §2.9.3 (p.68).

vector clause (§2.9.4, p.68)

  • Optional length: argument; selects vector mode (§2.9.4, p.68).

seq clause (§2.9.5, p.68)

  • Forces sequential execution of the associated loop (§2.9.5, p.68).

independent clause (§2.9.6, p.69)

  • Asserts absence of cross-iteration dependencies (§2.9.6, p.69).

auto clause (§2.9.7, p.69)

  • Delegates loop scheduling to implementation; interacts with routine clause inference (§2.9.7, p.69).

tile clause (§2.9.8, p.69)

  • Breaks iteration space into tile sizes; * uses runtime-determined tile length (§2.9.8, p.69).

device_type clause on loops (§2.9.9, p.70)

  • Restricts subsequent clauses to specified device types (§2.9.9, p.70).

device_resident clause (§2.13.1, pp.82–83)

  • Forces static device allocation with reference counting rules (§2.13.1, pp.82–83).
  • Creates persistent device linkages for large host data (§2.13.3, pp.83–84).

bind clause (§2.15.1, pp.93–94)

  • Sets alternate device symbol name (identifier or string) (§2.15.1, pp.93–94).

device_num and default_async clauses (§2.14.3, p.87)

  • Modify internal control variables acc-current-device-num-var and acc-default-async-var (§2.14.3, p.87).

nohost clause (§2.15.1, pp.94–95)

  • Suppresses host code generation for routines; cascades to dependent procedures (§2.15.1, pp.94–95).

finalize clause (§2.6.6, p.46)

  • Available on exit data; zeroes dynamic and attachment counters (§2.6.6, p.46).

wait-argument modifiers (§2.16, p.99)

  • devnum:int-expr: selects device; optional queues: prefix clarifies async argument list (§2.16, p.99).

async-value semantics (§2.16, p.98)

  • Maps async arguments to queue identifiers; acc_async_sync enforces synchronous completion, acc_async_noval uses default queue (§2.16, p.98).

OpenACC 3.4 Directive and Clause Restrictions

This digest enumerates every rule, restriction, and mandatory condition attached to OpenACC 3.4 directives and clauses. Entries are grouped by the section that defines the constraint. Page references match the official specification pagination so each item can be cross-checked word-for-word.

Compute constructs (§2.5.4, p.36)

  • Programs must not branch into or out of a compute construct.
  • Only the async, wait, num_gangs, num_workers, and vector_length clauses may follow a device_type clause on any compute construct.
  • At most one if clause may appear on a compute construct.
  • At most one default clause may appear and its value must be either none or present.
  • A reduction clause must not appear on a parallel construct whose num_gangs clause has more than one argument.

Compute construct errors (§2.5.5, p.37)

  • Errors raised by violating compute construct semantics follow §2.5.5; implementations must signal acc_error_host_only, acc_error_invalid_compute_region, acc_error_invalid_parallelism, or acc_error_invalid_matrix_shape as described in the specification when these conditions are detected.

Enter/exit data directives (§2.6.6, p.46)

  • enter data directives must include at least one of: copyin, create, or attach.
  • exit data directives must include at least one of: copyout, delete, or detach.
  • Only one if clause may appear on either directive.
  • finalize on exit data resets dynamic and attachment counters to zero for the listed variables; without it the counters are decremented normally.

Data environment (§2.6, pp.43–47)

  • Implicit data lifetime management must obey the reference counter semantics in §§2.6.3–2.6.8; structured and dynamic counters must never become negative.
  • Pointer attachments must respect the attach/detach counter pairing in §§2.6.7–2.6.8.

Host_data construct (§2.8, pp.62–63)

  • use_device lists must reference variables that are present on the device; otherwise behavior is undefined.
  • Host pointers aliased inside host_data regions must not be dereferenced on the host while mapped to device addresses.

Loop construct (§2.9, pp.64–71)

  • Only collapse, gang, worker, vector, seq, independent, auto, and tile clauses may follow a device_type clause.
  • worker and vector clause arguments must be invariant within the surrounding kernels region.
  • Loops without seq must satisfy: loop variable is integer/pointer/random-access iterator, iteration monotonicity, and constant-time trip count computation.
  • Only one of seq, independent, or auto may appear.
  • gang, worker, and vector clauses are mutually exclusive with an explicit seq clause.
  • A loop with a gang/worker/vector clause must not lexically enclose another loop with an equal or higher parallelism level unless the parent compute scope differs.
  • At most one gang clause may appear per loop construct.
  • tile and collapse must not be combined on loops associated with do concurrent.
  • Each associated loop in a tile construct (except the innermost) must contain exactly one loop or loop nest.
  • private clauses on loops must honor Fortran optional argument rules (§2.17.1, p.100).

Cache directive (§2.10, p.75)

  • References within the loop iteration must stay inside the index ranges listed in the cache directive.
  • Fortran optional arguments used in cache directives must follow §2.17.1.

Combined constructs (§2.11, p.76)

  • Combined constructs inherit all restrictions from their constituent parallel, serial, kernels, and loop components.

Atomic construct (§2.12, pp.77–81)

  • All atomic accesses to a given storage location must use the same type and type parameters.
  • The storage location designated by x must not exceed the hardware’s maximum native atomic width.
  • At most one if clause may appear on an atomic construct.

Declare directive (§2.13, pp.81–84)

  • declare must share scope with the declared variables (or enclosing function/module scope for Fortran).
  • At least one clause is required.
  • Clause arguments must be variable names or Fortran common block names; each variable may appear only once across declare clauses within a program unit.
  • Fortran assumed-size dummy arrays cannot appear; pointer arrays lose association on the device.
  • Fortran module declaration sections allow only create, copyin, device_resident, and link; C/C++ global scope allows only create, copyin, deviceptr, device_resident, and link.
  • C/C++ extern variables are limited to create, copyin, deviceptr, device_resident, and link.
  • link clauses must appear at global/module scope or reference extern/common-block entities.
  • declare regions must not contain longjmp/setjmp mismatches or uncaught C++ exceptions.
  • Fortran optional dummy arguments in data clauses must respect §2.17.1.

Init directive (§2.14.1, p.85)

  • May appear only in host code.
  • Re-initializing with different device types without shutting down is implementation-defined.
  • Initializing a device type not used by compiled accelerator regions yields undefined behavior.

Shutdown directive (§2.14.2, p.85)

  • May appear only in host code.

Set directive (§2.14.3, p.87)

  • Host-only directive.
  • default_async accepts only valid async identifiers; acc_async_noval has no effect, acc_async_sync forces synchronous execution on the default queue, and acc_async_default restores the initial queue.
  • Must include at least one of default_async, device_num, or device_type.
  • Duplicate clause kinds are forbidden on the same directive.

Update directive (§2.14.4, pp.88–90)

  • Requires at least one of self, host, or device clauses.
  • If if_present is absent, all listed variables must already be present on the device.
  • Only async and wait clauses may follow device_type.
  • At most one if clause may appear; it must evaluate to a scalar logical/integer value (Fortran vs C/C++ rules).
  • Noncontiguous subarrays are permitted; implementations may choose between multiple transfers or pack/unpack strategies but must not transfer outside the minimal containing contiguous region.
  • Struct/class and derived-type member restrictions follow §2.14.4; parent objects cannot simultaneously use subarray notation with member subarrays.
  • Fortran optional arguments in self, host, and device follow §2.17.1.
  • Directive must occupy a statement position (cannot replace the body after conditional headers or labels).

Wait directive (§2.16.3, p.100)

  • devnum values in wait-arguments must identify valid devices; invalid values trigger runtime errors (§2.16.3).
  • Queue identifiers must be valid async arguments or errors result (§2.16.3).

Routine directive (§2.15.1, pp.91–97)

  • Implicit routine directives derive from usage; implementations must propagate relevant clauses to dependent procedures and avoid infinite recursion when determining implicit attributes.
  • gang dimension argument must be an integer constant expression in {1,2,3}.
  • worker routines cannot be parents of gang routines; vector routines cannot be parents of worker or gang routines; seq routines cannot be parents of parallel routines.
  • Procedures compiled with nohost must not be called from host-only regions; enclosing procedures must also carry nohost when they call such routines.

Do concurrent integration (§2.17.2, pp.102–103)

  • When mapping Fortran do concurrent locality specs to OpenACC clauses, users must ensure host/device sharing matches the specified locality (e.g., local to private, local_init to firstprivate).

Data clauses (§§2.7–2.7.14, pp.48–60)

  • Clause arguments must reference contiguous array sections or pointer references as defined in §2.7.1.
  • Overlapping array sections in data clauses yield unspecified behavior (§2.7.1).
  • Modifier keywords (always, alwaysin, alwaysout, capture, readonly, zero) enforce the transfer behaviors in §2.7.4 and must not contradict device pointer semantics.
  • deviceptr variables cannot appear in other data clauses in the same region (§2.7.5).
  • present clauses require existing device data; absence triggers runtime errors (§2.7.6).
  • no_create forbids allocation and therefore requires prior presence (§2.7.11).
  • attach/detach must pair with pointer lifetimes and respect attachment counters (§§2.7.13–2.7.14).

Cache clause (§2.10, p.75)

  • Array references must remain within the listed cache ranges per iteration; violations are undefined.

Clause argument rules (§2.16, p.98)

  • async-argument values are limited to nonnegative integers or the special constants acc_async_default, acc_async_noval, acc_async_sync.
  • wait-argument syntax [devnum:int-expr:][queues:]async-argument-list requires valid device numbers and async identifiers.

OpenMP 6.0 Directives and Clauses

This catalogue is derived directly from the ROUP parser’s keyword registries and shows exactly which OpenMP 6.0 directive and clause tokens are recognised. The source of truth is OpenMpDirective::ALL and OpenMpClause::ALL in src/parser/openmp.rs. Every entry listed below is exercised by the automated support-matrix tests, so the tables always reflect parser reality.

For the normative meaning of each keyword, consult the OpenMP Application Programming Interface Version 6.0 specification.

Directive keywords (127 total)

allocatedistribute parallel loop simdparallel master tasklooptarget teams distribute parallel for simd
allocatorsdistribute simdparallel master taskloop simdtarget teams distribute parallel loop
assumedoparallel sectionstarget teams distribute parallel loop simd
assumesdo simdrequirestarget teams distribute simd
atomicend declare targetreversetarget teams loop
atomic captureerrorscantarget teams loop simd
atomic compare captureflushscopetarget update
atomic readforsectiontask
atomic updatefor simdsectionstask iteration
atomic writefusesimdtaskgraph
barriergroupprivatesingletaskgroup
begin assumesinterchangesplittaskloop
begin declare targetinteropstripetaskloop simd
begin declare variantlooptargettaskwait
begin metadirectivemaskedtarget datataskyield
cancelmasked tasklooptarget enter datateams
cancellation pointmasked taskloop simdtarget exit datateams distribute
criticalmastertarget loopteams distribute parallel do
declare inductionmetadirectivetarget loop simdteams distribute parallel do simd
declare mappernothingtarget parallelteams distribute parallel for
declare reductionorderedtarget parallel doteams distribute parallel for simd
declare simdparalleltarget parallel do simdteams distribute parallel loop
declare targetparallel dotarget parallel forteams distribute parallel loop simd
declare variantparallel do simdtarget parallel for simdteams distribute simd
depobjparallel fortarget parallel loopteams loop
dispatchparallel for simdtarget parallel loop simdteams loop simd
distributeparallel looptarget simdthreadprivate
distribute parallel doparallel loop simdtarget teamstile
distribute parallel do simdparallel maskedtarget teams distributeunroll
distribute parallel forparallel masked tasklooptarget teams distribute parallel doworkdistribute
distribute parallel for simdparallel masked taskloop simdtarget teams distribute parallel do simdworkshare
distribute parallel loopparallel mastertarget teams distribute parallel for

Clause keywords (132 total)

absentdoacrosslooprangereproducible
acq_reldynamic_allocatorsmapreverse
acquireentermatchreverse_offload
adjust_argsexclusivememscopesafelen
affinityfailmergeablesafesync
alignfiltermessageschedule
alignedfinalno_openmpself_maps
allocatefirstprivateno_openmp_constructsseq_cst
allocatorfromno_openmp_routinesseverity
append_argsfullno_parallelismshared
applygrainsizenocontextsimd
atgraph_idnogroupsimdlen
atomic_default_mem_ordergraph_resetnontemporalsizes
bindhas_device_addrnotinbranchtask_reduction
capturehintnovariantsthread_limit
collapseholdsnowaitthreads
collectorifnum_tasksthreadset
combinerin_reductionnum_teamstile
compareinbranchnum_threadsto
containsinclusiveordertransparent
copyinindirectorderedunified_address
copyprivateinductionotherwiseunified_shared_memory
countsinductorpartialuniform
defaultinitpermutationunroll
defaultmapinit_completepriorityuntied
dependinitializerprivateupdate
destroyinteropproc_binduse
detachis_device_ptrpublicuse_device_addr
devicelabelreaduse_device_ptr
device_residentlastprivatereductionuses_allocators
device_safesynclinearrelaxedweak
device_typelinkreleasewhen
dist_schedulelocalreplayablewrite

Keeping the list in sync

To regenerate the tables after changing src/parser/openmp.rs, run the helper script below and replace the output in this document:

python - <<'PY'
import math, pathlib, re
text = pathlib.Path('src/parser/openmp.rs').read_text()
block = re.search(r"openmp_directives!\s*{(.*?)}\s*\n\npub fn clause_registry", text, re.S).group(1)
directives = sorted({re.search(r'"([^"]+)"', line).group(1)
                      for line in block.splitlines() if '"' in line})
clauses_block = re.search(r"openmp_clauses!\s*{(.*?)}\s*\n\nmacro_rules!", text, re.S).group(1)
clauses = sorted({re.search(r'name: "([^"]+)"', line).group(1)
                   for line in clauses_block.splitlines() if 'name:' in line})

def make_table(items, columns=4):
    rows = math.ceil(len(items) / columns)
    table = ['| ' + ' | '.join([''] * columns) + ' |', '| ' + ' | '.join(['---'] * columns) + ' |']
    for r in range(rows):
        row = []
        for c in range(columns):
            idx = c * rows + r
            row.append(f"`{items[idx]}`" if idx < len(items) else '')
        table.append('| ' + ' | '.join(row) + ' |')
    return '\n'.join(table)

print(make_table(directives))
print('\n')
print(make_table(clauses))
PY

Keeping this document machine-derived guarantees it matches the parser at all times.

OpenMP 6.0 Directive–Clause Components

This reference summarises how the ROUP parser tokenises OpenMP 6.0 clause keywords. Rather than attempting to restate the entire specification, the information below mirrors the parser’s ClauseRule data so you can quickly see which textual forms are accepted. Combined constructs (for example parallel for or target teams distribute parallel for simd) are already part of the directive keyword registry listed in the directive catalogue.

Note: The OpenMP specification defines which directives may use a given clause and any semantic restrictions. ROUP currently enforces keyword syntax and delegates the normative rules to higher layers. Consult the OpenMP 6.0 specification for the full directive–clause compatibility matrix.

Clause syntax summary

ClauseParser ruleAccepted forms
absentParenthesizedabsent(...)
acq_relBare#pragma omp parallel acq_rel
acquireBare#pragma omp parallel acquire
adjust_argsParenthesizedadjust_args(...)
affinityParenthesizedaffinity(...)
alignParenthesizedalign(...)
alignedParenthesizedaligned(...)
allocateParenthesizedallocate(...)
allocatorParenthesizedallocator(...)
append_argsParenthesizedappend_args(...)
applyParenthesizedapply(...)
atParenthesizedat(...)
atomic_default_mem_orderParenthesizedatomic_default_mem_order(...)
bindParenthesizedbind(...)
captureFlexiblecapture or capture(...)
collapseParenthesizedcollapse(...)
collectorParenthesizedcollector(...)
combinerParenthesizedcombiner(...)
compareFlexiblecompare or compare(...)
containsParenthesizedcontains(...)
copyinParenthesizedcopyin(...)
copyprivateParenthesizedcopyprivate(...)
countsParenthesizedcounts(...)
defaultParenthesizeddefault(...)
defaultmapParenthesizeddefaultmap(...)
dependParenthesizeddepend(...)
destroyFlexibledestroy or destroy(...)
detachParenthesizeddetach(...)
deviceParenthesizeddevice(...)
device_residentParenthesizeddevice_resident(...)
device_safesyncFlexibledevice_safesync or device_safesync(...)
device_typeParenthesizeddevice_type(...)
dist_scheduleParenthesizeddist_schedule(...)
doacrossParenthesizeddoacross(...)
dynamic_allocatorsBare#pragma omp parallel dynamic_allocators
enterParenthesizedenter(...)
exclusiveBare#pragma omp parallel exclusive
failFlexiblefail or fail(...)
filterParenthesizedfilter(...)
finalParenthesizedfinal(...)
firstprivateParenthesizedfirstprivate(...)
fromParenthesizedfrom(...)
fullFlexiblefull or full(...)
grainsizeParenthesizedgrainsize(...)
graph_idParenthesizedgraph_id(...)
graph_resetParenthesizedgraph_reset(...)
has_device_addrParenthesizedhas_device_addr(...)
hintParenthesizedhint(...)
holdsParenthesizedholds(...)
ifParenthesizedif(...)
in_reductionParenthesizedin_reduction(...)
inbranchBare#pragma omp parallel inbranch
inclusiveBare#pragma omp parallel inclusive
indirectFlexibleindirect or indirect(...)
inductionParenthesizedinduction(...)
inductorParenthesizedinductor(...)
initParenthesizedinit(...)
init_completeFlexibleinit_complete or init_complete(...)
initializerParenthesizedinitializer(...)
interopParenthesizedinterop(...)
is_device_ptrParenthesizedis_device_ptr(...)
labelParenthesizedlabel(...)
lastprivateParenthesizedlastprivate(...)
linearParenthesizedlinear(...)
linkParenthesizedlink(...)
localParenthesizedlocal(...)
looprangeParenthesizedlooprange(...)
mapParenthesizedmap(...)
matchParenthesizedmatch(...)
memscopeParenthesizedmemscope(...)
mergeableBare#pragma omp parallel mergeable
messageParenthesizedmessage(...)
no_openmpFlexibleno_openmp or no_openmp(...)
no_openmp_constructsFlexibleno_openmp_constructs or no_openmp_constructs(...)
no_openmp_routinesFlexibleno_openmp_routines or no_openmp_routines(...)
no_parallelismFlexibleno_parallelism or no_parallelism(...)
nocontextParenthesizednocontext(...)
nogroupBare#pragma omp parallel nogroup
nontemporalParenthesizednontemporal(...)
notinbranchBare#pragma omp parallel notinbranch
novariantsFlexiblenovariants or novariants(...)
nowaitBare#pragma omp parallel nowait
num_tasksParenthesizednum_tasks(...)
num_teamsParenthesizednum_teams(...)
num_threadsParenthesizednum_threads(...)
orderParenthesizedorder(...)
orderedFlexibleordered or ordered(...)
otherwiseParenthesizedotherwise(...)
partialFlexiblepartial or partial(...)
permutationParenthesizedpermutation(...)
priorityParenthesizedpriority(...)
privateParenthesizedprivate(...)
proc_bindParenthesizedproc_bind(...)
publicFlexiblepublic or public(...)
readFlexibleread or read(...)
reductionParenthesizedreduction(...)
relaxedBare#pragma omp parallel relaxed
releaseBare#pragma omp parallel release
replayableFlexiblereplayable or replayable(...)
reproducibleBare#pragma omp parallel reproducible
reverseFlexiblereverse or reverse(...)
reverse_offloadBare#pragma omp parallel reverse_offload
safelenParenthesizedsafelen(...)
safesyncBare#pragma omp parallel safesync
scheduleParenthesizedschedule(...)
self_mapsBare#pragma omp parallel self_maps
seq_cstBare#pragma omp parallel seq_cst
severityParenthesizedseverity(...)
sharedParenthesizedshared(...)
simdBare#pragma omp parallel simd
simdlenParenthesizedsimdlen(...)
sizesParenthesizedsizes(...)
task_reductionParenthesizedtask_reduction(...)
thread_limitParenthesizedthread_limit(...)
threadsBare#pragma omp parallel threads
threadsetParenthesizedthreadset(...)
tileParenthesizedtile(...)
toParenthesizedto(...)
transparentFlexibletransparent or transparent(...)
unified_addressFlexibleunified_address or unified_address(...)
unified_shared_memoryFlexibleunified_shared_memory or unified_shared_memory(...)
uniformParenthesizeduniform(...)
unrollFlexibleunroll or unroll(...)
untiedBare#pragma omp parallel untied
updateFlexibleupdate or update(...)
useParenthesizeduse(...)
use_device_addrParenthesizeduse_device_addr(...)
use_device_ptrParenthesizeduse_device_ptr(...)
uses_allocatorsParenthesizeduses_allocators(...)
weakFlexibleweak or weak(...)
whenParenthesizedwhen(...)
writeFlexiblewrite or write(...)

Updating this index

The table can be regenerated with the following helper if new clauses are added or the parser changes a clause rule:

python - <<'PY'
import pathlib, re
text = pathlib.Path('src/parser/openmp.rs').read_text()
block = re.search(r"openmp_clauses!\s*{(.*?)}\s*\n\nmacro_rules!", text, re.S).group(1)
clauses = []
for entry in block.split('},'):
    name_match = re.search(r'name: "([^"]+)"', entry)
    rule_match = re.search(r'rule: ClauseRule::([A-Za-z_]+)', entry)
    if name_match and rule_match:
        clauses.append((name_match.group(1), rule_match.group(1)))
for name, rule in sorted(clauses):
    if rule == 'Bare':
        forms = f"`#pragma omp parallel {name}`"
    elif rule == 'Parenthesized':
        forms = f"`{name}(...)`"
    else:
        forms = f"`{name}` or `{name}(...)`"
    print(f"| `{name}` | {rule} | {forms} |")
PY

Copy the generated rows into the table above to keep this document aligned with OpenMpClause::ALL.

OpenMP 6.0 Restrictions

The OpenMP specification attaches normative “Restrictions” text to almost every directive and clause. Rather than duplicating that material (which quickly falls out of sync), this guide explains how to locate and verify the restrictions relevant to any construct while working on ROUP.

Where to look in the specification

  • Directive sections – Each directive definition in Chapters 5–17 of the OpenMP 6.0 specification ends with a Restrictions subsection. The text typically begins with “Restrictions to the <name> directive are as follows”.
  • Clause sections – Clause descriptions follow the same pattern, usually starting with “Restrictions to the <name> clause”.
  • Tables and modifiers – Map-type modifiers, dependence types, and other structured lists include restrictions inline with the tables that define the allowed values.
  • Language-specific notes – When the requirements differ between C/C++ and Fortran, the spec labels each bullet accordingly. Keep both variants in mind when adding parser validation or documentation.

Practical workflow for ROUP contributors

  1. Locate the canonical section using the directive and clause catalogues in this repository. Both openmp60-directives-clauses.md and openmp60-directive-clause-components.md link directly to the parser keywords.
  2. Read the specification subsection for the construct you are working on and note any “must”, “shall”, or “must not” statements. These are the normative requirements that need to be respected by higher-level tooling.
  3. Capture parser limitations in code comments or documentation if ROUP does not yet enforce a particular rule. This keeps behaviour transparent for users of the library and the ompparser compatibility layer.
  4. Add tests where feasible. Many restrictions can be unit- or integration-tested (for example, rejecting a clause form that is not permitted). When runtime enforcement is out of scope, reference the relevant specification section in the documentation so readers know where the gap is.

Keeping restriction notes accurate

  • Do not duplicate specification prose verbatim; link to the relevant section instead. This avoids stale text and respects the licence of the official document.
  • Record deviations whenever ROUP intentionally accepts syntax that the specification would forbid. Document the reasoning in the relevant module or test to make review easier.
  • Use citations (for example, §7.5.4) when summarising restrictions in release notes, tutorials, or design documents. It gives downstream users a precise pointer back to the standard.

By following this workflow the repository remains aligned with the official standard while still clearly communicating the parser’s current behaviour.

Line Continuations

⚠️ Experimental: ROUP now understands multi-line OpenMP directives across C, C++, and Fortran. This guide shows how to format continuations so the parser and ompparser compatibility layer recognize them reliably.

When to use continuations

OpenMP pragmas often grow long once multiple clauses are attached. Rather than forcing everything onto a single line, you can split directives while keeping source files readable. ROUP stitches the continued lines together during lexing, so downstream APIs still observe canonical single-line directive strings.

Continuations are supported in two situations:

  • C / C++ pragmas that end a line with a trailing backslash (\).
  • Fortran sentinels (!$OMP, C$OMP, *$OMP) that use the standard ampersand (&) continuation syntax.

ROUP preserves clause order and trims whitespace that was introduced only to align indentation.

C / C++ example

#pragma omp parallel for \
    schedule(dynamic, 4) \
    private(i, \
            j)

ROUP merges this directive into #pragma omp parallel for schedule(dynamic, 4) private(i, j). Clause arguments keep their original spacing. Comments (/* */ or //) may appear between continued lines and are ignored during merging.

Tips for C / C++

  • The backslash must be the final character on the line (aside from trailing spaces or tabs).
  • Windows line endings (\r\n) are handled automatically.
  • Keep at least one space between the directive name and the first clause on subsequent lines.

Fortran free-form example

!$omp target teams distribute &
!$omp parallel do &
!$omp& private(i, j)

The parser removes the continuation markers and produces !$omp target teams distribute parallel do private(i, j).

Fortran fixed-form example

      C$OMP   DO &
      !$OMP& SCHEDULE(DYNAMIC) &
      !$OMP PRIVATE(I) SHARED(A)

Column prefixes (!, C, or *) are respected. ROUP normalizes the directive to do schedule(DYNAMIC) private(I) shared(A).

Tips for Fortran continuations

  • Terminate every continued line with &. ROUP tolerates trailing comments (e.g., & ! explanation) and skips them automatically.
  • You may repeat the sentinel on continuation lines (!$OMP&), or start the next line with only &. Both forms are accepted.
  • Blank continuation lines are ignored as long as they contain only whitespace.
  • Clause bodies can span multiple lines; nested continuations inside parentheses are collapsed to a single line in the parsed clause value.

Troubleshooting

  • Missing continuation marker: If a line break appears without & (Fortran) or \ (C/C++), the parser treats the next line as a separate statement and reports an error or unexpected directive name.
  • Custom formatting macros: Preprocessors that insert trailing spaces after \ may break continuations. Ensure the backslash is the final printable character.
  • Compatibility layer: The ompparser shim mirrors the same behavior. The comprehensive tests in compat/ompparser/tests/comprehensive_test.cpp include multi-line inputs for both languages.

For more examples, refer to the automated tests in tests/openmp_line_continuations.rs and the parser unit tests in src/parser/mod.rs.

Architecture

This chapter explains how ROUP is organised internally and where to look when modifying the parser or the public bindings.

High level view

source text
   │
   ▼
lexer (`src/lexer.rs`)
   │  ─ token stream with language specific helpers
   ▼
parser (`src/parser/`)
   │  ─ builds an intermediate representation (IR)
   ▼
IR (`src/ir/`)
   │  ├─ used directly by Rust callers
   │  └─ converted into the C API data structures
   ▼
C bindings (`src/c_api.rs`)

The lexer normalises whitespace, line continuations, sentinel comments, and language specific keywords before the parser consumes the token stream. The parser modules mirror the OpenMP structure: directives, clauses, helper enumerations, and validation passes. Rust callers typically work with the IR structures directly, while C and C++ consumers receive stable C structs exposed through the FFI layer.

Unsafe code boundaries

The vast majority of the project uses safe Rust. The only unsafe blocks live inside src/c_api.rs where pointers cross the FFI boundary. Each function performs explicit null checks and documents its expectations. When modifying or adding FFI functions, keep the following rules in mind:

  • Convert raw pointers to Rust types as late as possible and convert back only when returning values to the caller.
  • Maintain ownership invariants: the caller is responsible for freeing values returned by constructors and must not free borrowed data.
  • Update the generated C constants header whenever the exported structs or enums change.

Generated constants and headers

The build script (build.rs) parses portions of src/c_api.rs using syn to produce src/roup_constants.h (also emitted to OUT_DIR). This keeps the OpenMP and OpenACC directive/clause tables in sync with the Rust implementation.

Compatibility layers

  • compat/ompparser/ mirrors the original ompparser API, forwarding calls through the ROUP C API and converting the resulting structures back into the expected C++ types. The CMake tests in the ompparser submodule validate parity.
  • compat/accparser/ provides the same drop-in experience for accparser with ROUP_ACC_* constants and the upstream ctest suite.

Testing

Integration tests live under tests/ and cover keyword registration, parser round-trips, language specific behaviour, and helper utilities. Running cargo test executes the Rust suites, while test.sh orchestrates the full project matrix including compatibility and documentation builds.

Contributing Guide

Thank you for your interest in contributing to ROUP! This guide will help you get started.



Ways to Contribute

1. Report Bugs

Found a bug? Please open an issue with:

  • Clear title: What’s wrong?
  • Input: The OpenMP directive that caused the issue
  • Expected: What should happen?
  • Actual: What actually happened?
  • Environment: OS, Rust version, ROUP version

Example:

Title: Parser fails on `collapse` clause with variable

Input: #pragma omp for collapse(n)
Expected: Parse successfully
Actual: Parse error: "Expected integer literal"
Environment: Ubuntu 22.04, Rust 1.75, ROUP 0.1.0
```text

### 2. Suggest Features

Have an idea? [Start a discussion](https://github.com/ouankou/roup/discussions) or open an issue with:

- **Use case**: Why is this needed?
- **Proposed API**: How would it work?
- **Alternatives**: Other ways to solve the problem?

### 3. Improve Documentation

Documentation improvements are always welcome:

- Fix typos or unclear explanations
- Add examples
- Improve error messages
- Translate documentation
- Write tutorials or blog posts

See [Documentation Updates](#documentation-updates) below.

### 4. Submit Code

Ready to code? See [Development Setup](#development-setup) and [Pull Request Process](#pull-request-process).

---

## Development Setup

### Prerequisites

- **Rust 1.88+** - [Install Rust](https://rustup.rs/)
- **Git** - Version control
- **mdBook** (optional) - For documentation: `cargo install mdbook`

### Clone and Build

```bash
# Clone repository
git clone https://github.com/ouankou/roup.git
cd roup

# Build library
cargo build

# Run tests
cargo test

# Build documentation
cargo doc --no-deps --open
```text

### Development Tools

**Recommended VS Code Extensions:**
- rust-analyzer
- Even Better TOML
- Error Lens

**Recommended CLI Tools:**
```bash
# Code formatter
rustup component add rustfmt

# Linter
rustup component add clippy

# Documentation builder
cargo install mdbook
```text

---

## Code Quality Standards

### Rust Code

#### 1. Use Safe Rust

**Rule**: Unsafe code is permitted ONLY at the FFI boundary in `src/c_api.rs`.

```rust
// ✅ GOOD: Safe Rust in parser
pub fn parse(input: &str) -> Result<DirectiveIR, ParseError> {
    // All safe code
}

// ❌ BAD: Unsafe in parser
pub fn parse(input: &str) -> Result<DirectiveIR, ParseError> {
    unsafe {  // ← Not allowed outside c_api.rs!
        // ...
    }
}
```text

#### 2. Format Your Code

```bash
# Format all code
cargo fmt

# Check formatting (CI uses this)
cargo fmt -- --check
```text

#### 3. Pass Clippy

```bash
# Run linter
cargo clippy

# CI requires no warnings
cargo clippy -- -D warnings
```text

#### 4. Write Tests

Every new feature or bug fix should include tests.

```rust
#[test]
fn test_new_feature() {
    let result = parse("#pragma omp my_new_directive");
    assert!(result.is_ok());
    // More assertions...
}
```text

#### 5. Document Public APIs

```rust
/// Parse an OpenMP directive from a string.
///
/// # Arguments
///
/// * `input` - The OpenMP directive text
///
/// # Returns
///
/// * `Ok(DirectiveIR)` - Parsed directive
/// * `Err(ParseError)` - Parse failure with location
///
/// # Examples
///
/// ```
/// use roup::parser::parse;
///
/// let directive = parse("#pragma omp parallel").unwrap();
/// assert_eq!(directive.clauses.len(), 0);
/// ```
pub fn parse(input: &str) -> Result<DirectiveIR, ParseError> {
    // ...
}
```text

### C API Code

If modifying `src/c_api.rs`:

- **Minimize unsafe blocks**: Only what's absolutely necessary
- **NULL checks**: Before every pointer dereference
- **Document safety contracts**: Explain caller obligations
- **Test thoroughly**: Including NULL inputs and edge cases

```rust
/// # Safety
///
/// Caller must ensure:
/// - `input` points to a valid null-terminated C string
/// - The string remains valid for the duration of this call
/// - The string is valid UTF-8
#[no_mangle]
pub extern "C" fn roup_parse(input: *const c_char) -> *mut OmpDirective {
    if input.is_null() {
        return std::ptr::null_mut();
    }
    
    // ... minimal unsafe code ...
}
```text

---

## Testing Guidelines

### Running Tests

```bash
# All tests
cargo test

# Specific test
cargo test test_parallel_directive

# Tests with output
cargo test -- --nocapture

# Tests in specific module
cargo test parser::
```text

### Test Categories

#### Unit Tests

Test individual functions in isolation.

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_tokenize_identifier() {
        let tokens = tokenize("parallel");
        assert_eq!(tokens.len(), 1);
        assert_eq!(tokens[0], Token::Identifier("parallel"));
    }
}
```text

#### Integration Tests

Test complete parsing workflows in `tests/`.

```rust
// tests/openmp_parallel.rs
#[test]
fn test_parallel_with_clauses() {
    let input = "#pragma omp parallel num_threads(4) private(x)";
    let result = roup::parser::parse(input);
    
    assert!(result.is_ok());
    let directive = result.unwrap();
    assert_eq!(directive.clauses.len(), 2);
}
```text

#### FFI Tests

Test C API safety and correctness.

```rust
#[test]
fn test_null_safety() {
    let dir = roup_parse(std::ptr::null());
    assert!(dir.is_null());
}
```text

### Test Coverage

Aim for:
- **90%+ coverage** for parser code
- **100% coverage** for FFI boundary code
- **All error paths** tested

---

## Documentation Updates

### mdBook Website

The main documentation is in `docs/book/src/`:

```text
docs/book/src/
├── SUMMARY.md           # Navigation (table of contents)
├── intro.md             # Homepage
├── getting-started.md   # Quick start guide
├── rust-tutorial.md     # Rust API tutorial
├── c-tutorial.md        # C API tutorial
├── cpp-tutorial.md      # C++ API tutorial
├── building.md          # Build instructions
├── api-reference.md     # API reference
├── architecture.md      # Internal design
├── openmp-support.md    # OpenMP support matrix
├── contributing.md      # This file
└── faq.md              # Frequently asked questions
```text

#### Building Documentation

```bash
# Build website
cd docs/book
mdbook build

# Serve locally (with live reload)
mdbook serve --open

# View at http://localhost:3000
```text

#### Adding New Pages

1. Create `.md` file in `docs/book/src/`
2. Add to `SUMMARY.md`:
   ```markdown
   - [My New Page](./my-new-page.md)
  1. Build and verify: mdbook build

Rustdoc

API documentation is generated from source code:

# Generate API docs
cargo doc --no-deps --open

# With private items (for development)
cargo doc --no-deps --document-private-items --open
```text

### README.md

**IMPORTANT**: After any change, check that `README.md` stays in sync:

- API changes → Update README examples
- Feature changes → Update README feature list
- Build changes → Update README installation instructions

The README should match website content in `docs/book/src/`.

---

## Pull Request Process

### 1. Fork and Branch

```bash
# Fork on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/roup.git
cd roup

# Create feature branch
git checkout -b feature/my-awesome-feature
```text

### 2. Make Changes

- Write code
- Write tests
- Update documentation
- Format code: `cargo fmt`
- Run tests: `cargo test`
- Check lints: `cargo clippy`

### 3. Commit

Use clear, descriptive commit messages:

```bash
git commit -m "feat: add support for OpenMP 6.0 loop directive"
git commit -m "fix: handle null pointers in roup_parse"
git commit -m "docs: add examples for metadirective"
git commit -m "test: add tests for error recovery"
```text

**Commit Message Format:**
- `feat:` - New feature
- `fix:` - Bug fix
- `docs:` - Documentation only
- `test:` - Tests only
- `refactor:` - Code refactoring
- `perf:` - Performance improvement
- `chore:` - Maintenance tasks

### 4. Pre-PR Checklist

Before opening a PR, ensure:

- [ ] `cargo fmt -- --check` passes (no formatting issues)
- [ ] `cargo build` passes (no compilation warnings)
- [ ] `cargo clippy` passes (no linter warnings)
- [ ] `cargo test` passes (all tests green)
- [ ] `cargo doc --no-deps` passes (no rustdoc warnings)
- [ ] `mdbook build docs/book` passes (if docs changed)
- [ ] README.md is in sync with changes
- [ ] New features have tests
- [ ] New features have documentation

### 5. Push and Open PR

```bash
# Push to your fork
git push origin feature/my-awesome-feature

# Open PR on GitHub
# Go to https://github.com/ouankou/roup and click "New Pull Request"
```text

### 6. PR Description

Include:

**What**: What does this PR do?

**Why**: Why is this change needed?

**How**: How does it work?

**Testing**: How was it tested?

**Example:**
```markdown
## What
Adds support for the OpenMP 6.0 `loop` directive.

## Why
OpenMP 6.0 introduced a new `loop` directive as a more generic alternative to `for`.

## How
- Added `Loop` variant to `DirectiveKind` enum
- Added parsing logic in `directive.rs`
- Updated OpenMP support matrix

## Testing
- Added 15 new test cases covering various `loop` directive forms
- All existing tests still pass
- Manually tested with real-world code
```text

### 7. Code Review

Maintainers will review your PR and may:

- Request changes
- Ask questions
- Suggest improvements

**Be patient and responsive!** Code review is a collaborative process.

### 8. Merge

Once approved, maintainers will merge your PR. Congratulations! 🎉

---

## OpenMP Specification Compliance

When adding support for new OpenMP features:

### 1. Consult Official Specs

- **OpenMP 6.0**: [Latest specification](https://www.openmp.org/specifications/)
- **Archive**: [Older versions](https://www.openmp.org/specifications/)

### 2. Check Syntax Carefully

OpenMP syntax can be subtle. Double-check:

- Required vs optional clauses
- Clause argument types
- Directive applicability (C/C++ vs Fortran)
- Version introduced

### 3. Update Support Matrix

After adding a directive/clause, update `docs/book/src/openmp-support.md`:

```markdown
| Directive | OpenMP Version | Status | Notes |
|-----------|----------------|--------|-------|
| `loop` | 5.0 | ✅ Supported | New in OpenMP 5.0 |
```text

### 4. Add Examples

Include examples in documentation showing correct usage.

---

## Performance Considerations

### Benchmarking

If your change affects performance:

```bash
# Run benchmarks (if available)
cargo bench

# Profile with flamegraph
cargo install flamegraph
cargo flamegraph --bin roup
```text

### Performance Guidelines

- Avoid unnecessary allocations
- Prefer zero-copy when possible
- Use `&str` instead of `String` where appropriate
- Benchmark before/after for significant changes

---

## Security

### Reporting Security Issues

Please report security vulnerabilities by opening a GitHub issue at:
[https://github.com/ouankou/roup/issues](https://github.com/ouankou/roup/issues)

Include:
- Description of vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)

### Security Best Practices

When writing code:

- Validate all inputs
- Check for integer overflow
- Avoid buffer overruns
- Be careful with unsafe code
- Use safe defaults

---

## Release Process

(For maintainers)

### Version Numbering

ROUP follows [Semantic Versioning](https://semver.org/):

- **MAJOR**: Breaking API changes
- **MINOR**: New features (backward compatible)
- **PATCH**: Bug fixes (backward compatible)

### Release Checklist

1. Update version in `Cargo.toml`
2. Update `CHANGELOG.md`
3. Run full test suite
4. Build documentation
5. Create git tag: `git tag v0.2.0`
6. Push tag: `git push origin v0.2.0`
7. Publish to crates.io: `cargo publish`
8. Create GitHub release with notes

---

## Getting Help

### Stuck?

- **Documentation**: Read [roup.ouankou.com](https://roup.ouankou.com)
- **Discussions**: Ask on [GitHub Discussions](https://github.com/ouankou/roup/discussions)
- **Issues**: Search [existing issues](https://github.com/ouankou/roup/issues)
- **Examples**: Check `examples/` directory

### Communication Guidelines

- Be respectful and professional
- Provide context for questions
- Include minimal reproducible examples
- Search before asking (avoid duplicates)

---

## Code of Conduct

### Our Standards

- **Be respectful**: Treat everyone with respect
- **Be constructive**: Provide helpful feedback
- **Be patient**: Remember that everyone is learning
- **Be inclusive**: Welcome newcomers

### Unacceptable Behavior

- Harassment or discrimination
- Trolling or insulting comments
- Personal attacks
- Publishing others' private information

### Reporting

Report unacceptable behavior to: [conduct@ouankou.com](mailto:conduct@ouankou.com)

---

## Recognition

Contributors will be:

- Listed in `CONTRIBUTORS.md`
- Mentioned in release notes
- Credited in commit messages

Significant contributions may result in:

- Maintainer status
- Commit access
- Decision-making authority

---

## License

By contributing, you agree that your contributions will be licensed under the same license as the project (see `LICENSE` file).

---

## Questions?

Still have questions? Open a [discussion](https://github.com/ouankou/roup/discussions) or reach out to the maintainers.

**Thank you for contributing to ROUP!** 🚀

Frequently Asked Questions

General

What is ROUP?

ROUP is a Rust library that parses OpenMP and OpenACC directives and exposes the result to Rust, C, C++, and Fortran consumers. It focuses on analysing and transforming existing pragma-based code rather than compiling it.

Is ROUP production ready?

Not yet. The project is actively developed and APIs may change between releases. Treat the current builds as experimental and review the release notes for the latest status updates.

Which versions are supported?

  • OpenMP: Up to 6.0. Integration tests exercise the keyword registry, loop-transform directives, metadirectives, and device constructs.
  • OpenACC: 3.4 with full directive/clause coverage and aliases. Unsupported constructs fail with descriptive parse errors instead of being silently accepted.

Installation

How do I install ROUP for Rust?

Add the crate to your Cargo.toml:

[dependencies]
roup = "0.7"

How do I use ROUP from C or C++?

Build the library with cargo build --release and link against the generated artefact (libroup.so, libroup.dylib, or roup.dll). The generated constants header lives at src/roup_constants.h. See the C/C++ tutorials for function prototypes and RAII helpers.

What toolchains are required?

  • Rust 1.88 or newer (matches the MSRV in Cargo.toml).
  • A C/C++ compiler when using the FFI bindings.
  • Optional: Fortran compiler for the Fortran examples.

Usage

How do I parse a directive?

use roup::parser::openmp;

let parser = openmp::parser();
let (_, directive) = parser.parse("#pragma omp parallel for").expect("parse");
println!("kind: {}", directive.name);

How do I walk clauses from C?

OmpClauseIterator* it = roup_directive_clauses_iter(dir);
OmpClause* clause = NULL;
while (roup_clause_iterator_next(it, &clause)) {
    printf("clause kind: %d\n", roup_clause_kind(clause));
}
roup_clause_iterator_free(it);

Testing and contributing

Which tests should I run before sending a change?

At minimum run cargo test. For thorough coverage run ./test.sh and ./test_rust_versions.sh (see TESTING.md for details). These scripts build examples, execute the ompparser and accparser compatibility tests, and verify the documentation.

Where can I learn more?