moved the C compilation commands into a struct for easier handling
diff --git a/crates/intrinsic-test/src/arm/functions.rs b/crates/intrinsic-test/src/arm/functions.rs
index 8158dfd..6f39e4a 100644
--- a/crates/intrinsic-test/src/arm/functions.rs
+++ b/crates/intrinsic-test/src/arm/functions.rs
@@ -2,6 +2,7 @@
 use super::intrinsic::ArmIntrinsicType;
 use crate::arm::constraint::Constraint;
 use crate::common::argument::Argument;
+use crate::common::compile_c::CompilationCommandBuilder;
 use crate::common::format::Indentation;
 use crate::common::gen_c::{compile_c, create_c_filenames, generate_c_program};
 use crate::common::gen_rust::{compile_rust, create_rust_filenames, generate_rust_program};
@@ -161,70 +162,55 @@
 
 fn compile_c_arm(
     intrinsics_name_list: &Vec<String>,
-    filename_mapping: BTreeMap<&String, String>,
+    _filename_mapping: BTreeMap<&String, String>,
     compiler: &str,
     target: &str,
     cxx_toolchain_dir: Option<&str>,
 ) -> bool {
-    let compiler_commands = intrinsics_name_list.iter().map(|intrinsic_name| {
-        let c_filename = filename_mapping.get(intrinsic_name).unwrap();
-        let flags = std::env::var("CPPFLAGS").unwrap_or("".into());
-        let arch_flags = if target.contains("v7") {
-            "-march=armv8.6-a+crypto+crc+dotprod+fp16"
-        } else {
-            "-march=armv8.6-a+crypto+sha3+crc+dotprod+fp16+faminmax+lut"
-        };
+    let mut command = CompilationCommandBuilder::new()
+        .add_arch_flags(vec!["armv8.6-a", "crypto", "crc", "dotprod", "fp16"])
+        .set_compiler(compiler)
+        .set_target(target)
+        .set_opt_level("2")
+        .set_cxx_toolchain_dir(cxx_toolchain_dir)
+        .set_project_root("c_programs")
+        .add_extra_flags(vec!["-ffp-contract=off", "-Wno-narrowing"]);
 
-        let compiler_command = if target == "aarch64_be-unknown-linux-gnu" {
-            let Some(cxx_toolchain_dir) = cxx_toolchain_dir else {
-                panic!(
-                    "When setting `--target aarch64_be-unknown-linux-gnu` the C++ compilers toolchain directory must be set with `--cxx-toolchain-dir <dest>`"
-                );
-            };
+    if !target.contains("v7") {
+        command = command.add_arch_flags(vec!["faminmax", "lut", "sha3"]);
+    }
 
-            /* clang++ cannot link an aarch64_be object file, so we invoke
-             * aarch64_be-unknown-linux-gnu's C++ linker. This ensures that we
-             * are testing the intrinsics against LLVM.
-             *
-             * Note: setting `--sysroot=<...>` which is the obvious thing to do
-             * does not work as it gets caught up with `#include_next <stdlib.h>`
-             * not existing... */
-            format!(
-                "{compiler} {flags} {arch_flags} \
-                -ffp-contract=off \
-                -Wno-narrowing \
-                -O2 \
-                --target=aarch64_be-unknown-linux-gnu \
-                -I{cxx_toolchain_dir}/include \
-                -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include \
-                -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include/c++/14.2.1 \
-                -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include/c++/14.2.1/aarch64_be-none-linux-gnu \
-                -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/include/c++/14.2.1/backward \
-                -I{cxx_toolchain_dir}/aarch64_be-none-linux-gnu/libc/usr/include \
-                -c {c_filename} \
-                -o c_programs/{intrinsic_name}.o && \
-                {cxx_toolchain_dir}/bin/aarch64_be-none-linux-gnu-g++ c_programs/{intrinsic_name}.o -o c_programs/{intrinsic_name} && \
-                rm c_programs/{intrinsic_name}.o",
+    command = if target == "aarch64_be-unknown-linux-gnu" {
+        command
+            .set_linker(
+                cxx_toolchain_dir.unwrap_or("").to_string() + "/bin/aarch64_be-none-linux-gnu-g++",
             )
+            .set_include_paths(vec![
+                "/include",
+                "/aarch64_be-none-linux-gnu/include",
+                "/aarch64_be-none-linux-gnu/include/c++/14.2.1",
+                "/aarch64_be-none-linux-gnu/include/c++/14.2.1/aarch64_be-none-linux-gnu",
+                "/aarch64_be-none-linux-gnu/include/c++/14.2.1/backward",
+                "/aarch64_be-none-linux-gnu/libc/usr/include",
+            ])
+    } else {
+        if compiler.contains("clang") {
+            command.add_extra_flag(format!("-target {target}").as_str())
         } else {
-            // -ffp-contract=off emulates Rust's approach of not fusing separate mul-add operations
-            let base_compiler_command = format!(
-                "{compiler} {flags} {arch_flags} -o c_programs/{intrinsic_name} {c_filename} -ffp-contract=off -Wno-narrowing -O2"
-            );
+            command.add_extra_flag("-flax-vector-conversions")
+        }
+    };
 
-            /* `-target` can be passed to some c++ compilers, however if we want to
-             *   use a c++ compiler does not support this flag we do not want to pass
-             *   the flag. */
-            if compiler.contains("clang") {
-                format!("{base_compiler_command} -target {target}")
-            } else {
-                format!("{base_compiler_command} -flax-vector-conversions")
-            }
-        };
-
-        compiler_command
-    })
-    .collect::<Vec<_>>();
+    let compiler_commands = intrinsics_name_list
+        .iter()
+        .map(|intrinsic_name| {
+            command
+                .clone()
+                .set_input_name(intrinsic_name)
+                .set_output_name(intrinsic_name)
+                .to_string()
+        })
+        .collect::<Vec<_>>();
 
     compile_c(&compiler_commands)
 }
diff --git a/crates/intrinsic-test/src/common/compile_c.rs b/crates/intrinsic-test/src/common/compile_c.rs
new file mode 100644
index 0000000..f018e02
--- /dev/null
+++ b/crates/intrinsic-test/src/common/compile_c.rs
@@ -0,0 +1,151 @@
+#[derive(Clone)]
+pub struct CompilationCommandBuilder {
+    compiler: String,
+    target: Option<String>,
+    cxx_toolchain_dir: Option<String>,
+    arch_flags: Vec<String>,
+    optimization: String,
+    include_paths: Vec<String>,
+    project_root: Option<String>,
+    output: String,
+    input: String,
+    linker: Option<String>,
+    extra_flags: Vec<String>,
+}
+
+impl CompilationCommandBuilder {
+    pub fn new() -> Self {
+        Self {
+            compiler: String::new(),
+            target: None,
+            cxx_toolchain_dir: None,
+            arch_flags: Vec::new(),
+            optimization: "2".to_string(),
+            include_paths: Vec::new(),
+            project_root: None,
+            output: String::new(),
+            input: String::new(),
+            linker: None,
+            extra_flags: Vec::new(),
+        }
+    }
+
+    pub fn set_compiler(mut self, compiler: &str) -> Self {
+        self.compiler = compiler.to_string();
+        self
+    }
+
+    pub fn set_target(mut self, target: &str) -> Self {
+        self.target = Some(target.to_string());
+        self
+    }
+
+    pub fn set_cxx_toolchain_dir(mut self, path: Option<&str>) -> Self {
+        self.cxx_toolchain_dir = path.map(|p| p.to_string());
+        self
+    }
+
+    pub fn add_arch_flags(mut self, flags: Vec<&str>) -> Self {
+        let mut new_arch_flags = flags.into_iter().map(|v| v.to_string()).collect();
+        self.arch_flags.append(&mut new_arch_flags);
+
+        self
+    }
+
+    pub fn set_opt_level(mut self, optimization: &str) -> Self {
+        self.optimization = optimization.to_string();
+        self
+    }
+
+    /// Sets a list of include paths for compilation.
+    /// The paths that are passed must be relative to the
+    /// "cxx_toolchain_dir" directory path.
+    pub fn set_include_paths(mut self, paths: Vec<&str>) -> Self {
+        self.include_paths = paths.into_iter().map(|path| path.to_string()).collect();
+        self
+    }
+
+    /// Sets the root path of all the generated test files.
+    pub fn set_project_root(mut self, path: &str) -> Self {
+        self.project_root = Some(path.to_string());
+        self
+    }
+
+    /// The name of the output executable, without any suffixes
+    pub fn set_output_name(mut self, path: &str) -> Self {
+        self.output = path.to_string();
+        self
+    }
+
+    /// The name of the input C file, without any suffixes
+    pub fn set_input_name(mut self, path: &str) -> Self {
+        self.input = path.to_string();
+        self
+    }
+
+    pub fn set_linker(mut self, linker: String) -> Self {
+        self.linker = Some(linker);
+        self.output += ".o";
+        self
+    }
+
+    pub fn add_extra_flags(mut self, flags: Vec<&str>) -> Self {
+        let mut flags: Vec<String> = flags.into_iter().map(|f| f.to_string()).collect();
+        self.extra_flags.append(&mut flags);
+        self
+    }
+
+    pub fn add_extra_flag(self, flag: &str) -> Self {
+        self.add_extra_flags(vec![flag])
+    }
+}
+
+impl CompilationCommandBuilder {
+    pub fn to_string(self) -> String {
+        let arch_flags = self.arch_flags.join("+");
+        let flags = std::env::var("CPPFLAGS").unwrap_or("".into());
+        let project_root = self.project_root.unwrap_or(String::new());
+        let project_root_str = project_root.as_str();
+        let mut command = format!(
+            "{} {flags} -march={arch_flags} \
+            -O{} \
+            -o {project_root}/{} \
+            {project_root}/{}.cpp",
+            self.compiler, self.optimization, self.output, self.input,
+        );
+
+        command = command + " " + self.extra_flags.join(" ").as_str();
+
+        if let (Some(linker), Some(cxx_toolchain_dir)) = (&self.linker, &self.cxx_toolchain_dir) {
+            if let Some(target) = &self.target {
+                command = command + " --target=" + target;
+            }
+
+            let include_args = self
+                .include_paths
+                .iter()
+                .map(|path| "--include-directory=".to_string() + cxx_toolchain_dir + path)
+                .collect::<Vec<_>>()
+                .join(" ");
+
+            command = command
+                + " -c "
+                + include_args.as_str()
+                + " && "
+                + linker
+                + project_root_str
+                + "/"
+                + self.output.as_str()
+                + " -o "
+                + project_root_str
+                + "/"
+                + self.output.strip_suffix(".o").unwrap()
+                + " && rm "
+                + project_root_str
+                + "/"
+                + self.output.as_str();
+        }
+
+        command
+    }
+}
diff --git a/crates/intrinsic-test/src/common/mod.rs b/crates/intrinsic-test/src/common/mod.rs
index 7db1166..ae44eb0 100644
--- a/crates/intrinsic-test/src/common/mod.rs
+++ b/crates/intrinsic-test/src/common/mod.rs
@@ -4,6 +4,7 @@
 
 pub mod argument;
 pub mod compare;
+pub mod compile_c;
 pub mod format;
 pub mod gen_c;
 pub mod gen_rust;