Skip to content

Commit 691bf14

Browse files
committed
rust: transformation module todo fixed
1 parent 75c6cb7 commit 691bf14

File tree

1 file changed

+204
-57
lines changed

1 file changed

+204
-57
lines changed

rust/bear/src/semantic/transformation.rs

Lines changed: 204 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,81 +6,103 @@
66
//! It can also alter the compiler flags of the compiler calls. The actions
77
//! are defined in the configuration this module is given.
88
9-
use crate::config;
10-
use crate::semantic;
11-
use crate::semantic::Transform;
9+
use crate::{config, semantic};
1210

13-
pub enum Transformation {
14-
None,
15-
Config(Vec<config::Compiler>),
11+
use std::collections::HashMap;
12+
use std::path::PathBuf;
13+
14+
pub struct Transformation {
15+
compilers: HashMap<PathBuf, Vec<config::Compiler>>,
1616
}
1717

1818
impl From<&config::Output> for Transformation {
1919
fn from(config: &config::Output) -> Self {
2020
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,
21+
config::Output::Clang { compilers, .. } => compilers.as_slice().into(),
22+
config::Output::Semantic { .. } => Transformation::new(),
23+
}
24+
}
25+
}
26+
27+
impl From<&[config::Compiler]> for Transformation {
28+
fn from(config: &[config::Compiler]) -> Self {
29+
let mut compilers = HashMap::new();
30+
for compiler in config {
31+
compilers
32+
.entry(compiler.path.clone())
33+
.or_insert_with(Vec::new)
34+
.push(compiler.clone());
3035
}
36+
Transformation { compilers }
3137
}
3238
}
3339

34-
impl Transform for Transformation {
40+
impl semantic::Transform for Transformation {
3541
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-
})
68-
}
69-
None => Some(input),
42+
if let Some(configs) = self.compilers.get(&input.compiler) {
43+
Self::apply_when_not_empty(configs.as_slice(), input)
44+
} else {
45+
Some(input)
7046
}
7147
}
7248
}
7349

7450
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,
51+
fn new() -> Self {
52+
Transformation {
53+
compilers: HashMap::new(),
54+
}
55+
}
56+
57+
/// Apply the transformation to the compiler call.
58+
///
59+
/// Multiple configurations can be applied to the same compiler call.
60+
/// And depending on the instruction from the configuration, the compiler call
61+
/// can be ignored, modified, or left unchanged. The conditional ignore will
62+
/// check if the compiler call matches the flags defined in the configuration.
63+
fn apply_when_not_empty(
64+
configs: &[config::Compiler],
65+
input: semantic::CompilerCall,
66+
) -> Option<semantic::CompilerCall> {
67+
let mut current_input = Some(input);
68+
69+
for config in configs {
70+
current_input = match config {
71+
config::Compiler {
72+
ignore: config::IgnoreOrConsider::Always,
73+
..
74+
} => None,
75+
config::Compiler {
76+
ignore: config::IgnoreOrConsider::Conditional,
77+
arguments,
78+
..
79+
} => current_input.filter(|input| !Self::match_condition(arguments, &input.passes)),
80+
config::Compiler {
81+
ignore: config::IgnoreOrConsider::Never,
82+
arguments,
83+
..
84+
} => current_input.map(|input| semantic::CompilerCall {
85+
compiler: input.compiler.clone(),
86+
working_dir: input.working_dir.clone(),
87+
passes: Transformation::apply_argument_changes(
88+
arguments,
89+
input.passes.as_slice(),
90+
),
91+
}),
92+
};
93+
94+
if current_input.is_none() {
95+
break;
96+
}
8097
}
98+
current_input
8199
}
82100

83-
fn filter(arguments: &config::Arguments, passes: &[semantic::CompilerPass]) -> bool {
101+
/// Check if the compiler call matches the condition defined in the configuration.
102+
///
103+
/// Any compiler pass that matches the flags defined in the configuration will cause
104+
/// the whole compiler call to be ignored.
105+
fn match_condition(arguments: &config::Arguments, passes: &[semantic::CompilerPass]) -> bool {
84106
let match_flags = arguments.match_.as_slice();
85107
passes.iter().any(|pass| match pass {
86108
semantic::CompilerPass::Compile { flags, .. } => {
@@ -90,7 +112,11 @@ impl Transformation {
90112
})
91113
}
92114

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

0 commit comments

Comments
 (0)