Skip to content

Commit 4624020

Browse files
committed
rust: transformation module todo fixed
1 parent cbeabc8 commit 4624020

File tree

1 file changed

+198
-54
lines changed

1 file changed

+198
-54
lines changed

rust/bear/src/semantic/transformation.rs

Lines changed: 198 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,79 +8,98 @@
88
99
use crate::config;
1010
use crate::semantic;
11-
use crate::semantic::Transform;
1211

13-
pub enum Transformation {
14-
None,
15-
Config(Vec<config::Compiler>),
12+
pub struct Transformation {
13+
compilers: Vec<config::Compiler>,
1614
}
1715

1816
impl From<&config::Output> for Transformation {
1917
fn from(config: &config::Output) -> Self {
2018
match config {
21-
config::Output::Clang { compilers, .. } => {
22-
if compilers.is_empty() {
23-
Transformation::None
24-
} else {
25-
let compilers = compilers.clone();
26-
Transformation::Config(compilers)
27-
}
28-
}
29-
config::Output::Semantic { .. } => Transformation::None,
19+
config::Output::Clang { compilers, .. } => Transformation {
20+
compilers: compilers.clone(),
21+
},
22+
config::Output::Semantic { .. } => Transformation { compilers: vec![] },
3023
}
3124
}
3225
}
3326

34-
impl Transform for Transformation {
27+
impl semantic::Transform for Transformation {
28+
/// Apply the transformation to the compiler call.
29+
///
30+
/// Optimize for the case when the configuration is empty.
3531
fn apply(&self, input: semantic::CompilerCall) -> Option<semantic::CompilerCall> {
36-
let semantic::CompilerCall {
37-
compiler,
38-
passes,
39-
working_dir,
40-
} = &input;
41-
match self.lookup(compiler) {
42-
Some(config::Compiler {
43-
ignore: config::IgnoreOrConsider::Always,
44-
..
45-
}) => None,
46-
Some(config::Compiler {
47-
ignore: config::IgnoreOrConsider::Conditional,
48-
arguments,
49-
..
50-
}) => {
51-
if Self::filter(arguments, passes) {
52-
None
53-
} else {
54-
Some(input)
55-
}
56-
}
57-
Some(config::Compiler {
58-
ignore: config::IgnoreOrConsider::Never,
59-
arguments,
60-
..
61-
}) => {
62-
let new_passes = Transformation::execute(arguments, passes);
63-
Some(semantic::CompilerCall {
64-
compiler: compiler.clone(),
65-
working_dir: working_dir.clone(),
66-
passes: new_passes,
67-
})
32+
if self.compilers.is_empty() {
33+
Some(input)
34+
} else {
35+
// Find the configurations that match the compiler name.
36+
let compiler = &input.compiler.clone();
37+
let configs: Vec<_> = self
38+
.compilers
39+
.iter()
40+
.filter(|config| config.path == *compiler)
41+
.collect();
42+
43+
// Apply the transformation if there are any configurations.
44+
if configs.is_empty() {
45+
Some(input)
46+
} else {
47+
Self::apply_when_not_empty(configs.as_slice(), input)
6848
}
69-
None => Some(input),
7049
}
7150
}
7251
}
7352

7453
impl Transformation {
75-
// TODO: allow multiple matches for the same compiler
76-
fn lookup(&self, compiler: &std::path::Path) -> Option<&config::Compiler> {
77-
match self {
78-
Transformation::Config(compilers) => compilers.iter().find(|c| c.path == compiler),
79-
_ => None,
54+
/// Apply the transformation to the compiler call.
55+
///
56+
/// Multiple configurations can be applied to the same compiler call.
57+
/// And depending on the instruction from the configuration, the compiler call
58+
/// can be ignored, modified, or left unchanged. The conditional ignore will
59+
/// check if the compiler call matches the flags defined in the configuration.
60+
fn apply_when_not_empty(
61+
configs: &[&config::Compiler],
62+
input: semantic::CompilerCall,
63+
) -> Option<semantic::CompilerCall> {
64+
let mut current_input = Some(input);
65+
66+
for config in configs {
67+
current_input = match config {
68+
config::Compiler {
69+
ignore: config::IgnoreOrConsider::Always,
70+
..
71+
} => None,
72+
config::Compiler {
73+
ignore: config::IgnoreOrConsider::Conditional,
74+
arguments,
75+
..
76+
} => current_input.filter(|input| !Self::match_condition(arguments, &input.passes)),
77+
config::Compiler {
78+
ignore: config::IgnoreOrConsider::Never,
79+
arguments,
80+
..
81+
} => current_input.map(|input| semantic::CompilerCall {
82+
compiler: input.compiler.clone(),
83+
working_dir: input.working_dir.clone(),
84+
passes: Transformation::apply_argument_changes(
85+
arguments,
86+
input.passes.as_slice(),
87+
),
88+
}),
89+
};
90+
91+
if current_input.is_none() {
92+
break;
93+
}
8094
}
95+
current_input
8196
}
8297

83-
fn filter(arguments: &config::Arguments, passes: &[semantic::CompilerPass]) -> bool {
98+
/// Check if the compiler call matches the condition defined in the configuration.
99+
///
100+
/// Any compiler pass that matches the flags defined in the configuration will cause
101+
/// the whole compiler call to be ignored.
102+
fn match_condition(arguments: &config::Arguments, passes: &[semantic::CompilerPass]) -> bool {
84103
let match_flags = arguments.match_.as_slice();
85104
passes.iter().any(|pass| match pass {
86105
semantic::CompilerPass::Compile { flags, .. } => {
@@ -90,7 +109,11 @@ impl Transformation {
90109
})
91110
}
92111

93-
fn execute(
112+
/// Apply the changes defined in the configuration to the compiler call.
113+
///
114+
/// The changes can be to remove or add flags to the compiler call.
115+
/// Only the flags will be changed, but applies to all compiler passes.
116+
fn apply_argument_changes(
94117
arguments: &config::Arguments,
95118
passes: &[semantic::CompilerPass],
96119
) -> Vec<semantic::CompilerPass> {
@@ -122,3 +145,124 @@ impl Transformation {
122145
new_passes
123146
}
124147
}
148+
149+
#[cfg(test)]
150+
mod tests {
151+
use super::*;
152+
use crate::config::{Arguments, Compiler, IgnoreOrConsider};
153+
use crate::semantic::{CompilerCall, CompilerPass, Transform};
154+
use std::path::PathBuf;
155+
156+
#[test]
157+
fn test_apply_no_filter() {
158+
let input = CompilerCall {
159+
compiler: std::path::PathBuf::from("gcc"),
160+
passes: vec![CompilerPass::Compile {
161+
source: PathBuf::from("main.c"),
162+
output: PathBuf::from("main.o").into(),
163+
flags: vec!["-O2".into()],
164+
}],
165+
working_dir: std::path::PathBuf::from("/project"),
166+
};
167+
168+
let sut = Transformation { compilers: vec![] };
169+
let result = sut.apply(input);
170+
171+
let expected = CompilerCall {
172+
compiler: std::path::PathBuf::from("gcc"),
173+
passes: vec![CompilerPass::Compile {
174+
source: PathBuf::from("main.c"),
175+
output: PathBuf::from("main.o").into(),
176+
flags: vec!["-O2".into()],
177+
}],
178+
working_dir: std::path::PathBuf::from("/project"),
179+
};
180+
assert_eq!(result, Some(expected));
181+
}
182+
183+
#[test]
184+
fn test_apply_filter_match() {
185+
let input = CompilerCall {
186+
compiler: std::path::PathBuf::from("cc"),
187+
passes: vec![CompilerPass::Compile {
188+
source: PathBuf::from("main.c"),
189+
output: PathBuf::from("main.o").into(),
190+
flags: vec!["-O2".into()],
191+
}],
192+
working_dir: std::path::PathBuf::from("/project"),
193+
};
194+
195+
let sut = Transformation {
196+
compilers: vec![Compiler {
197+
path: std::path::PathBuf::from("cc"),
198+
ignore: IgnoreOrConsider::Always,
199+
arguments: Arguments::default(),
200+
}],
201+
};
202+
let result = sut.apply(input);
203+
assert!(result.is_none());
204+
}
205+
206+
#[test]
207+
fn test_apply_conditional_match() {
208+
let input = CompilerCall {
209+
compiler: std::path::PathBuf::from("gcc"),
210+
passes: vec![CompilerPass::Compile {
211+
source: PathBuf::from("main.c"),
212+
output: PathBuf::from("main.o").into(),
213+
flags: vec!["-O2".into(), "-Wall".into()],
214+
}],
215+
working_dir: std::path::PathBuf::from("/project"),
216+
};
217+
218+
let sut = Transformation {
219+
compilers: vec![Compiler {
220+
path: std::path::PathBuf::from("gcc"),
221+
ignore: IgnoreOrConsider::Conditional,
222+
arguments: Arguments {
223+
match_: vec!["-O2".into()],
224+
..Arguments::default()
225+
},
226+
}],
227+
};
228+
let result = sut.apply(input);
229+
assert!(result.is_none());
230+
}
231+
232+
#[test]
233+
fn test_apply_ignore_never_modify_arguments() {
234+
let input = CompilerCall {
235+
compiler: std::path::PathBuf::from("gcc"),
236+
passes: vec![CompilerPass::Compile {
237+
source: PathBuf::from("main.c"),
238+
output: PathBuf::from("main.o").into(),
239+
flags: vec!["-O2".into()],
240+
}],
241+
working_dir: std::path::PathBuf::from("/project"),
242+
};
243+
244+
let sut = Transformation {
245+
compilers: vec![Compiler {
246+
path: std::path::PathBuf::from("gcc"),
247+
ignore: IgnoreOrConsider::Never,
248+
arguments: Arguments {
249+
add: vec!["-Wall".into()],
250+
remove: vec!["-O2".into()],
251+
..Arguments::default()
252+
},
253+
}],
254+
};
255+
let result = sut.apply(input);
256+
257+
let expected = CompilerCall {
258+
compiler: std::path::PathBuf::from("gcc"),
259+
passes: vec![CompilerPass::Compile {
260+
source: PathBuf::from("main.c"),
261+
output: PathBuf::from("main.o").into(),
262+
flags: vec!["-Wall".into()],
263+
}],
264+
working_dir: std::path::PathBuf::from("/project"),
265+
};
266+
assert_eq!(result, Some(expected));
267+
}
268+
}

0 commit comments

Comments
 (0)