sleigh_compiler/
compiler.rs

1use std::{collections::BTreeMap, path::Path};
2
3use crate::ffi::sys::{self, PreprocessorDefine};
4use cxx::UniquePtr;
5
6pub type SleighCompilerResponse = sys::CompileResponse;
7
8#[derive(thiserror::Error, Debug)]
9pub enum Error {
10    #[error("non-zero exit code: {exit_code}", exit_code = .0.exit_code)]
11    NonZeroExitCode(SleighCompilerResponse),
12
13    #[error("internal compiler error: {0}")]
14    InternalCompilerError(Box<dyn std::error::Error + Send + Sync>),
15}
16
17pub type Result<T> = std::result::Result<T, Error>;
18
19/// The primary interface for interacting with the Sleigh compiler
20pub struct SleighCompiler {
21    compiler: UniquePtr<sys::SleighCompileProxy>,
22}
23
24/// Available compiler options exposed by the Sleigh compiler
25pub struct SleighCompilerOptions {
26    /// Map of variable to value that is passed to the preprocessor
27    pub defines: BTreeMap<String, String>,
28
29    /// Set to `true` to enable individual warnings about unnecessary p-code ops
30    pub unnecessary_pcode_warnings: bool,
31
32    /// Set to `false` to report indistinguishable patterns as errors
33    pub lenient_conflict: bool,
34
35    /// Set to `true` for individual warnings about constructors with colliding operands
36    pub all_collision_warning: bool,
37
38    /// Set to `true` for individual warnings about NOP constructors
39    pub all_nop_warning: bool,
40
41    /// Set to `true` for individual warnings about dead temporary varnodes
42    pub dead_temp_warning: bool,
43
44    /// Set to `true` to force all local variable definitions to use the `local` keyword
45    pub enforce_local_keyword: bool,
46
47    /// Set to `true` for individual warnings about temporary varnodes that are too large
48    pub large_temporary_warning: bool,
49
50    /// Set to `true` if register names are allowed to be case-sensitive
51    pub case_sensitive_register_names: bool,
52
53    /// Set to `true` to write the output file using the debug (XML) form of the .sla format
54    pub debug_output: bool,
55}
56
57impl Default for SleighCompilerOptions {
58    fn default() -> Self {
59        // These are the default options defined in slgh_compile.cc
60        Self {
61            defines: Default::default(),
62            unnecessary_pcode_warnings: false,
63            lenient_conflict: true,
64            all_collision_warning: false,
65            all_nop_warning: false,
66            dead_temp_warning: false,
67            enforce_local_keyword: false,
68            large_temporary_warning: false,
69            case_sensitive_register_names: false,
70            debug_output: false,
71        }
72    }
73}
74
75impl SleighCompiler {
76    /// Construct a new compiler instance configured with the given options. Use
77    /// [SleighCompiler::default] to create an instance with default options.
78    pub fn new(options: SleighCompilerOptions) -> Self {
79        let mut compiler = sys::new_sleigh_compiler();
80
81        let mut defines = cxx::CxxVector::new();
82        for (name, value) in options.defines {
83            defines.pin_mut().push(PreprocessorDefine { name, value });
84        }
85
86        compiler.pin_mut().set_all_options(
87            defines.as_ref().unwrap(),
88            options.unnecessary_pcode_warnings,
89            options.lenient_conflict,
90            options.all_collision_warning,
91            options.all_nop_warning,
92            options.dead_temp_warning,
93            options.enforce_local_keyword,
94            options.large_temporary_warning,
95            options.case_sensitive_register_names,
96            options.debug_output,
97        );
98
99        Self { compiler }
100    }
101
102    /// Invoke the compiler on the provided `.slaspec` input file. The output `.sla` file will be
103    /// written to the given output path. On success will return the compiler response with a `0`
104    /// exit code. All warnings are reproduced in the `warnings` field.
105    pub fn compile(
106        &mut self,
107        input_slaspec_path: impl AsRef<Path>,
108        output_sla_path: impl AsRef<Path>,
109    ) -> Result<SleighCompilerResponse> {
110        cxx::let_cxx_string!(filein = input_slaspec_path.as_ref().as_os_str().as_encoded_bytes());
111        cxx::let_cxx_string!(fileout = output_sla_path.as_ref().as_os_str().as_encoded_bytes());
112
113        let response = self
114            .compiler
115            .pin_mut()
116            .run_compilation(filein.as_ref(), fileout.as_ref())
117            .map_err(|err| Error::InternalCompilerError(Box::new(err)))?;
118
119        if response.exit_code == 0 {
120            Ok(response)
121        } else {
122            Err(Error::NonZeroExitCode(response))
123        }
124    }
125}
126
127impl Default for SleighCompiler {
128    fn default() -> Self {
129        Self::new(SleighCompilerOptions::default())
130    }
131}